Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-01-25 18:12:32 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-25 18:12:32 +0300
commit7d8d5a3dab415672a41ab29c3bfa9581f275dc50 (patch)
tree7b9249d8ca8c12ad899b4e6d968193d58e63f458 /spec/frontend/issues/show
parent868c8c35fbddd439f4df76a5954e2a1caa2af3cc (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.js270
-rw-r--r--spec/frontend/issues/show/mock_data/mock_data.js14
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>
+`;