diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-25 18:12:32 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-25 18:12:32 +0300 |
commit | 7d8d5a3dab415672a41ab29c3bfa9581f275dc50 (patch) | |
tree | 7b9249d8ca8c12ad899b4e6d968193d58e63f458 /spec/frontend/issues/show | |
parent | 868c8c35fbddd439f4df76a5954e2a1caa2af3cc (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/issues/show')
-rw-r--r-- | spec/frontend/issues/show/components/description_spec.js | 270 | ||||
-rw-r--r-- | spec/frontend/issues/show/mock_data/mock_data.js | 14 |
2 files changed, 188 insertions, 96 deletions
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js index d39e00b9c9e..df6f7cb827d 100644 --- a/spec/frontend/issues/show/components/description_spec.js +++ b/spec/frontend/issues/show/components/description_spec.js @@ -1,21 +1,56 @@ import $ from 'jquery'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import '~/behaviors/markdown/render_gfm'; +import { GlPopover, GlModal } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { stubComponent } from 'helpers/stub_component'; import { TEST_HOST } from 'helpers/test_constants'; -import mountComponent from 'helpers/vue_mount_component_helper'; import Description from '~/issues/show/components/description.vue'; import TaskList from '~/task_list'; -import { descriptionProps as props } from '../mock_data/mock_data'; +import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; +import { + descriptionProps as initialProps, + descriptionHtmlWithCheckboxes, +} from '../mock_data/mock_data'; jest.mock('~/task_list'); +const showModal = jest.fn(); +const hideModal = jest.fn(); + describe('Description component', () => { - let vm; - let DescriptionComponent; + let wrapper; + + 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 findPopovers = () => wrapper.findAllComponents(GlPopover); + const findModal = () => wrapper.findComponent(GlModal); + const findCreateWorkItem = () => wrapper.findComponent(CreateWorkItem); + + function createComponent({ props = {}, provide = {} } = {}) { + wrapper = shallowMount(Description, { + propsData: { + ...initialProps, + ...props, + }, + provide, + stubs: { + GlModal: stubComponent(GlModal, { + methods: { + show: showModal, + hide: hideModal, + }, + }), + GlPopover, + }, + }); + } beforeEach(() => { - DescriptionComponent = Vue.extend(Description); - if (!document.querySelector('.issuable-meta')) { const metaData = document.createElement('div'); metaData.classList.add('issuable-meta'); @@ -24,91 +59,102 @@ describe('Description component', () => { document.body.appendChild(metaData); } - - vm = mountComponent(DescriptionComponent, props); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); afterAll(() => { $('.issuable-meta .flash-container').remove(); }); - it('doesnt animate first description changes', () => { - vm.descriptionHtml = 'changed'; - - return vm.$nextTick().then(() => { - expect( - vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'), - ).toBeFalsy(); - jest.runAllTimers(); - return vm.$nextTick(); + it('doesnt animate first description changes', async () => { + createComponent(); + await wrapper.setProps({ + descriptionHtml: 'changed', }); + + expect(findGfmContent().classes()).not.toContain('issue-realtime-pre-pulse'); }); - it('animates description changes on live update', () => { - vm.descriptionHtml = 'changed'; - return vm - .$nextTick() - .then(() => { - vm.descriptionHtml = 'changed second time'; - return vm.$nextTick(); - }) - .then(() => { - expect( - vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'), - ).toBeTruthy(); - jest.runAllTimers(); - return vm.$nextTick(); - }) - .then(() => { - expect( - vm.$el.querySelector('.md').classList.contains('issue-realtime-trigger-pulse'), - ).toBeTruthy(); - }); + it('animates description changes on live update', async () => { + createComponent(); + await wrapper.setProps({ + descriptionHtml: 'changed', + }); + + expect(findGfmContent().classes()).not.toContain('issue-realtime-pre-pulse'); + + await wrapper.setProps({ + descriptionHtml: 'changed second time', + }); + + expect(findGfmContent().classes()).toContain('issue-realtime-pre-pulse'); + + await jest.runOnlyPendingTimers(); + + expect(findGfmContent().classes()).toContain('issue-realtime-trigger-pulse'); }); - it('applies syntax highlighting and math when description changed', () => { - const vmSpy = jest.spyOn(vm, 'renderGFM'); + it('applies syntax highlighting and math when description changed', async () => { const prototypeSpy = jest.spyOn($.prototype, 'renderGFM'); - vm.descriptionHtml = 'changed'; + createComponent(); - return vm.$nextTick().then(() => { - expect(vm.$refs['gfm-content']).toBeDefined(); - expect(vmSpy).toHaveBeenCalled(); - expect(prototypeSpy).toHaveBeenCalled(); - expect($.prototype.renderGFM).toHaveBeenCalled(); + await wrapper.setProps({ + descriptionHtml: 'changed', }); + + expect(findGfmContent().exists()).toBe(true); + expect(prototypeSpy).toHaveBeenCalled(); }); it('sets data-update-url', () => { - expect(vm.$el.querySelector('textarea').dataset.updateUrl).toEqual(TEST_HOST); + createComponent(); + expect(findTextarea().attributes('data-update-url')).toBe(TEST_HOST); }); describe('TaskList', () => { beforeEach(() => { - vm.$destroy(); TaskList.mockClear(); - vm = mountComponent(DescriptionComponent, { ...props, issuableType: 'issuableType' }); }); it('re-inits the TaskList when description changed', () => { - vm.descriptionHtml = 'changed'; + createComponent({ + props: { + issuableType: 'issuableType', + }, + }); + wrapper.setProps({ + descriptionHtml: 'changed', + }); expect(TaskList).toHaveBeenCalled(); }); - it('does not re-init the TaskList when canUpdate is false', () => { - vm.canUpdate = false; - vm.descriptionHtml = 'changed'; + it('does not re-init the TaskList when canUpdate is false', async () => { + createComponent({ + props: { + issuableType: 'issuableType', + canUpdate: false, + }, + }); + wrapper.setProps({ + descriptionHtml: 'changed', + }); - expect(TaskList).toHaveBeenCalledTimes(1); + expect(TaskList).not.toHaveBeenCalled(); }); it('calls with issuableType dataType', () => { - vm.descriptionHtml = 'changed'; + createComponent({ + props: { + issuableType: 'issuableType', + }, + }); + wrapper.setProps({ + descriptionHtml: 'changed', + }); expect(TaskList).toHaveBeenCalledWith({ dataType: 'issuableType', @@ -123,65 +169,97 @@ describe('Description component', () => { }); describe('taskStatus', () => { - it('adds full taskStatus', () => { - vm.taskStatus = '1 of 1'; - - return vm.$nextTick().then(() => { - expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe( - '1 of 1', - ); + it('adds full taskStatus', async () => { + createComponent({ + props: { + taskStatus: '1 of 1', + }, }); - }); + await nextTick(); - it('adds short taskStatus', () => { - vm.taskStatus = '1 of 1'; + expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe( + '1 of 1', + ); + }); - return vm.$nextTick().then(() => { - expect(document.querySelector('.issuable-meta #task_status_short').textContent.trim()).toBe( - '1/1 task', - ); + it('adds short taskStatus', async () => { + createComponent({ + props: { + taskStatus: '1 of 1', + }, }); - }); + await nextTick(); - it('clears task status text when no tasks are present', () => { - vm.taskStatus = '0 of 0'; + expect(document.querySelector('.issuable-meta #task_status_short').textContent.trim()).toBe( + '1/1 task', + ); + }); - return vm.$nextTick().then(() => { - expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe(''); + it('clears task status text when no tasks are present', async () => { + createComponent({ + props: { + taskStatus: '0 of 0', + }, }); + + await nextTick(); + + expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe(''); }); }); - describe('taskListUpdateStarted', () => { - it('emits event to parent', () => { - const spy = jest.spyOn(vm, '$emit'); - - vm.taskListUpdateStarted(); + describe('with work items feature flag is enabled', () => { + beforeEach(async () => { + createComponent({ + props: { + descriptionHtml: descriptionHtmlWithCheckboxes, + }, + provide: { + glFeatures: { + workItems: true, + }, + }, + }); + await nextTick(); + }); - expect(spy).toHaveBeenCalledWith('taskListUpdateStarted'); + it('renders a list of hidden buttons corresponding to checkboxes in description HTML', () => { + expect(findTaskActionButtons()).toHaveLength(3); }); - }); - describe('taskListUpdateSuccess', () => { - it('emits event to parent', () => { - const spy = jest.spyOn(vm, '$emit'); + it('renders a list of popovers corresponding to checkboxes in description HTML', () => { + expect(findPopovers()).toHaveLength(3); + expect(findPopovers().at(0).props('target')).toBe( + findTaskActionButtons().at(0).attributes('id'), + ); + }); - vm.taskListUpdateSuccess(); + it('does not show a modal by default', () => { + expect(findModal().props('visible')).toBe(false); + }); - expect(spy).toHaveBeenCalledWith('taskListUpdateSucceeded'); + 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(); + expect(findCreateWorkItem().props('initialTitle').trim()).toBe('todo 1'); }); - }); - describe('taskListUpdateError', () => { - it('should create flash notification and emit an event to parent', () => { - const msg = - 'Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again.'; - const spy = jest.spyOn(vm, '$emit'); + it('closes the modal on `closeCreateTaskModal` event', () => { + findConvertToTaskButton().vm.$emit('click'); + findCreateWorkItem().vm.$emit('closeModal'); + expect(hideModal).toHaveBeenCalled(); + }); - vm.taskListUpdateError(); + it('updates description HTML on `onCreate` event', async () => { + const newTitle = 'New title'; + findConvertToTaskButton().vm.$emit('click'); + findCreateWorkItem().vm.$emit('onCreate', newTitle); + expect(hideModal).toHaveBeenCalled(); + await nextTick(); - expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(msg); - expect(spy).toHaveBeenCalledWith('taskListUpdateFailed'); + expect(findTaskSvg().exists()).toBe(true); + expect(wrapper.text()).toContain(newTitle); }); }); }); diff --git a/spec/frontend/issues/show/mock_data/mock_data.js b/spec/frontend/issues/show/mock_data/mock_data.js index a73826954c3..89653ff82b2 100644 --- a/spec/frontend/issues/show/mock_data/mock_data.js +++ b/spec/frontend/issues/show/mock_data/mock_data.js @@ -58,3 +58,17 @@ export const appProps = { zoomMeetingUrl, publishedIncidentUrl, }; + +export const descriptionHtmlWithCheckboxes = ` + <ul dir="auto" class="task-list" data-sourcepos"3:1-5:12"> + <li class="task-list-item" data-sourcepos="3:1-3:11"> + <input class="task-list-item-checkbox" type="checkbox"> todo 1 + </li> + <li class="task-list-item" data-sourcepos="4:1-4:12"> + <input class="task-list-item-checkbox" type="checkbox"> todo 2 + </li> + <li class="task-list-item" data-sourcepos="5:1-5:12"> + <input class="task-list-item-checkbox" type="checkbox"> todo 3 + </li> + </ul> +`; |