debian-mirror-gitlab/spec/frontend/issues/show/components/description_spec.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

391 lines
11 KiB
JavaScript
Raw Normal View History

2018-05-09 12:01:36 +05:30
import $ from 'jquery';
2022-04-04 11:22:00 +05:30
import { nextTick } from 'vue';
2019-12-04 20:38:33 +05:30
import '~/behaviors/markdown/render_gfm';
2022-06-21 17:19:12 +05:30
import { GlTooltip, GlModal } from '@gitlab/ui';
import setWindowLocation from 'helpers/set_window_location_helper';
2022-04-04 11:22:00 +05:30
import { stubComponent } from 'helpers/stub_component';
2020-05-24 23:13:21 +05:30
import { TEST_HOST } from 'helpers/test_constants';
2022-05-07 20:08:51 +05:30
import { mockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
2022-01-26 12:08:38 +05:30
import Description from '~/issues/show/components/description.vue';
2022-06-21 17:19:12 +05:30
import { updateHistory } from '~/lib/utils/url_utility';
2020-05-24 23:13:21 +05:30
import TaskList from '~/task_list';
2022-05-07 20:08:51 +05:30
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
2022-04-04 11:22:00 +05:30
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import {
descriptionProps as initialProps,
descriptionHtmlWithCheckboxes,
2022-06-21 17:19:12 +05:30
descriptionHtmlWithTask,
2022-04-04 11:22:00 +05:30
} from '../mock_data/mock_data';
2020-05-24 23:13:21 +05:30
2022-05-07 20:08:51 +05:30
jest.mock('~/flash');
2022-06-21 17:19:12 +05:30
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
updateHistory: jest.fn(),
}));
2020-05-24 23:13:21 +05:30
jest.mock('~/task_list');
2017-09-10 17:25:29 +05:30
2022-04-04 11:22:00 +05:30
const showModal = jest.fn();
const hideModal = jest.fn();
2022-06-21 17:19:12 +05:30
const $toast = {
show: jest.fn(),
};
2022-04-04 11:22:00 +05:30
2017-09-10 17:25:29 +05:30
describe('Description component', () => {
2022-04-04 11:22:00 +05:30
let wrapper;
const findGfmContent = () => wrapper.find('[data-testid="gfm-content"]');
const findTextarea = () => wrapper.find('[data-testid="textarea"]');
const findTaskActionButtons = () => wrapper.findAll('.js-add-task');
2022-06-21 17:19:12 +05:30
const findConvertToTaskButton = () => wrapper.find('.js-add-task');
2022-04-04 11:22:00 +05:30
2022-06-21 17:19:12 +05:30
const findTooltips = () => wrapper.findAllComponents(GlTooltip);
2022-04-04 11:22:00 +05:30
const findModal = () => wrapper.findComponent(GlModal);
const findCreateWorkItem = () => wrapper.findComponent(CreateWorkItem);
2022-05-07 20:08:51 +05:30
const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal);
2022-04-04 11:22:00 +05:30
function createComponent({ props = {}, provide = {} } = {}) {
2022-05-07 20:08:51 +05:30
wrapper = shallowMountExtended(Description, {
2022-04-04 11:22:00 +05:30
propsData: {
2022-06-21 17:19:12 +05:30
issueId: 1,
2022-04-04 11:22:00 +05:30
...initialProps,
...props,
},
provide,
2022-06-21 17:19:12 +05:30
mocks: {
$toast,
},
2022-04-04 11:22:00 +05:30
stubs: {
GlModal: stubComponent(GlModal, {
methods: {
show: showModal,
hide: hideModal,
},
}),
},
});
}
2017-09-10 17:25:29 +05:30
beforeEach(() => {
2022-06-21 17:19:12 +05:30
setWindowLocation(TEST_HOST);
2017-09-10 17:25:29 +05:30
if (!document.querySelector('.issuable-meta')) {
const metaData = document.createElement('div');
metaData.classList.add('issuable-meta');
2019-03-02 22:35:43 +05:30
metaData.innerHTML =
'<div class="flash-container"></div><span id="task_status"></span><span id="task_status_short"></span>';
2017-09-10 17:25:29 +05:30
document.body.appendChild(metaData);
}
2018-03-17 18:26:18 +05:30
});
afterEach(() => {
2022-04-04 11:22:00 +05:30
wrapper.destroy();
2017-09-10 17:25:29 +05:30
});
2019-03-02 22:35:43 +05:30
afterAll(() => {
$('.issuable-meta .flash-container').remove();
});
2022-04-04 11:22:00 +05:30
it('doesnt animate first description changes', async () => {
createComponent();
await wrapper.setProps({
descriptionHtml: 'changed',
2020-11-24 15:15:51 +05:30
});
2022-04-04 11:22:00 +05:30
expect(findGfmContent().classes()).not.toContain('issue-realtime-pre-pulse');
2020-11-24 15:15:51 +05:30
});
2022-04-04 11:22:00 +05:30
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');
2017-09-10 17:25:29 +05:30
});
2022-04-04 11:22:00 +05:30
it('applies syntax highlighting and math when description changed', async () => {
2020-05-24 23:13:21 +05:30
const prototypeSpy = jest.spyOn($.prototype, 'renderGFM');
2022-04-04 11:22:00 +05:30
createComponent();
2018-10-15 14:42:47 +05:30
2022-04-04 11:22:00 +05:30
await wrapper.setProps({
descriptionHtml: 'changed',
2020-05-24 23:13:21 +05:30
});
2022-04-04 11:22:00 +05:30
expect(findGfmContent().exists()).toBe(true);
expect(prototypeSpy).toHaveBeenCalled();
2020-05-24 23:13:21 +05:30
});
it('sets data-update-url', () => {
2022-04-04 11:22:00 +05:30
createComponent();
expect(findTextarea().attributes('data-update-url')).toBe(TEST_HOST);
2020-05-24 23:13:21 +05:30
});
describe('TaskList', () => {
2018-03-17 18:26:18 +05:30
beforeEach(() => {
2020-05-24 23:13:21 +05:30
TaskList.mockClear();
2018-03-17 18:26:18 +05:30
});
2020-05-24 23:13:21 +05:30
it('re-inits the TaskList when description changed', () => {
2022-04-04 11:22:00 +05:30
createComponent({
props: {
issuableType: 'issuableType',
},
});
wrapper.setProps({
descriptionHtml: 'changed',
});
2018-03-17 18:26:18 +05:30
2020-05-24 23:13:21 +05:30
expect(TaskList).toHaveBeenCalled();
2018-03-17 18:26:18 +05:30
});
2022-04-04 11:22:00 +05:30
it('does not re-init the TaskList when canUpdate is false', async () => {
createComponent({
props: {
issuableType: 'issuableType',
canUpdate: false,
},
});
wrapper.setProps({
descriptionHtml: 'changed',
});
2018-03-17 18:26:18 +05:30
2022-04-04 11:22:00 +05:30
expect(TaskList).not.toHaveBeenCalled();
2018-03-17 18:26:18 +05:30
});
2020-05-24 23:13:21 +05:30
it('calls with issuableType dataType', () => {
2022-04-04 11:22:00 +05:30
createComponent({
props: {
issuableType: 'issuableType',
},
});
wrapper.setProps({
descriptionHtml: 'changed',
});
2018-03-17 18:26:18 +05:30
2020-05-24 23:13:21 +05:30
expect(TaskList).toHaveBeenCalledWith({
dataType: 'issuableType',
fieldName: 'description',
selector: '.detail-page-description',
2021-12-11 22:18:48 +05:30
onUpdate: expect.any(Function),
onSuccess: expect.any(Function),
2020-05-24 23:13:21 +05:30
onError: expect.any(Function),
lockVersion: 0,
2018-03-17 18:26:18 +05:30
});
});
});
2017-09-10 17:25:29 +05:30
describe('taskStatus', () => {
2022-04-04 11:22:00 +05:30
it('adds full taskStatus', async () => {
createComponent({
props: {
taskStatus: '1 of 1',
},
2017-09-10 17:25:29 +05:30
});
2022-04-04 11:22:00 +05:30
await nextTick();
2017-09-10 17:25:29 +05:30
2022-04-04 11:22:00 +05:30
expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe(
'1 of 1',
);
});
2017-09-10 17:25:29 +05:30
2022-04-04 11:22:00 +05:30
it('adds short taskStatus', async () => {
createComponent({
props: {
taskStatus: '1 of 1',
},
2017-09-10 17:25:29 +05:30
});
2022-04-04 11:22:00 +05:30
await nextTick();
2017-09-10 17:25:29 +05:30
2022-04-04 11:22:00 +05:30
expect(document.querySelector('.issuable-meta #task_status_short').textContent.trim()).toBe(
'1/1 task',
);
});
2017-09-10 17:25:29 +05:30
2022-04-04 11:22:00 +05:30
it('clears task status text when no tasks are present', async () => {
createComponent({
props: {
taskStatus: '0 of 0',
},
2017-09-10 17:25:29 +05:30
});
2022-04-04 11:22:00 +05:30
await nextTick();
expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe('');
2017-09-10 17:25:29 +05:30
});
});
2022-04-04 11:22:00 +05:30
describe('with work items feature flag is enabled', () => {
describe('empty description', () => {
2022-05-07 20:08:51 +05:30
beforeEach(() => {
2022-04-04 11:22:00 +05:30
createComponent({
props: {
descriptionHtml: '',
},
provide: {
glFeatures: {
workItems: true,
},
},
});
2022-05-07 20:08:51 +05:30
return nextTick();
2022-04-04 11:22:00 +05:30
});
2021-12-11 22:18:48 +05:30
2022-04-04 11:22:00 +05:30
it('renders without error', () => {
expect(findTaskActionButtons()).toHaveLength(0);
});
2021-12-11 22:18:48 +05:30
});
2022-04-04 11:22:00 +05:30
describe('description with checkboxes', () => {
2022-05-07 20:08:51 +05:30
beforeEach(() => {
2022-04-04 11:22:00 +05:30
createComponent({
props: {
descriptionHtml: descriptionHtmlWithCheckboxes,
},
provide: {
glFeatures: {
workItems: true,
},
},
});
2022-05-07 20:08:51 +05:30
return nextTick();
2022-04-04 11:22:00 +05:30
});
2021-12-11 22:18:48 +05:30
2022-04-04 11:22:00 +05:30
it('renders a list of hidden buttons corresponding to checkboxes in description HTML', () => {
expect(findTaskActionButtons()).toHaveLength(3);
});
2021-12-11 22:18:48 +05:30
2022-06-21 17:19:12 +05:30
it('renders a list of tooltips corresponding to checkboxes in description HTML', () => {
expect(findTooltips()).toHaveLength(3);
expect(findTooltips().at(0).props('target')).toBe(
2022-04-04 11:22:00 +05:30
findTaskActionButtons().at(0).attributes('id'),
);
});
2021-12-11 22:18:48 +05:30
2022-04-04 11:22:00 +05:30
it('does not show a modal by default', () => {
expect(findModal().props('visible')).toBe(false);
});
2019-03-02 22:35:43 +05:30
2022-06-21 17:19:12 +05:30
it('opens a modal when a button is clicked and displays correct title', async () => {
await findConvertToTaskButton().trigger('click');
2022-04-04 11:22:00 +05:30
expect(findCreateWorkItem().props('initialTitle').trim()).toBe('todo 1');
});
2022-06-21 17:19:12 +05:30
it('closes the modal on `closeCreateTaskModal` event', async () => {
await findConvertToTaskButton().trigger('click');
2022-04-04 11:22:00 +05:30
findCreateWorkItem().vm.$emit('closeModal');
expect(hideModal).toHaveBeenCalled();
});
2019-03-02 22:35:43 +05:30
2022-06-21 17:19:12 +05:30
it('emits `updateDescription` on `onCreate` event', () => {
const newDescription = `<p>New description</p>`;
findCreateWorkItem().vm.$emit('onCreate', newDescription);
2022-04-04 11:22:00 +05:30
expect(hideModal).toHaveBeenCalled();
2022-06-21 17:19:12 +05:30
expect(wrapper.emitted('updateDescription')).toEqual([[newDescription]]);
});
it('shows toast after delete success', async () => {
findWorkItemDetailModal().vm.$emit('workItemDeleted');
2022-04-04 11:22:00 +05:30
2022-06-21 17:19:12 +05:30
expect($toast.show).toHaveBeenCalledWith('Work item deleted');
2022-04-04 11:22:00 +05:30
});
2019-03-02 22:35:43 +05:30
});
2022-05-07 20:08:51 +05:30
describe('work items detail', () => {
2022-06-21 17:19:12 +05:30
const findTaskLink = () => wrapper.find('a.gfm-issue');
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
describe('when opening and closing', () => {
beforeEach(() => {
createComponent({
props: {
descriptionHtml: descriptionHtmlWithTask,
},
provide: {
glFeatures: { workItems: true },
},
});
return nextTick();
2022-05-07 20:08:51 +05:30
});
2022-06-21 17:19:12 +05:30
it('opens when task button is clicked', async () => {
expect(findWorkItemDetailModal().props('visible')).toBe(false);
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
await findTaskLink().trigger('click');
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
expect(findWorkItemDetailModal().props('visible')).toBe(true);
expect(updateHistory).toHaveBeenCalledWith({
url: `${TEST_HOST}/?work_item_id=2`,
replace: true,
});
});
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
it('closes from an open state', async () => {
await findTaskLink().trigger('click');
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
expect(findWorkItemDetailModal().props('visible')).toBe(true);
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
findWorkItemDetailModal().vm.$emit('close');
await nextTick();
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
expect(findWorkItemDetailModal().props('visible')).toBe(false);
expect(updateHistory).toHaveBeenLastCalledWith({
url: `${TEST_HOST}/`,
replace: true,
});
});
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
it('tracks when opened', async () => {
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
await findTaskLink().trigger('click');
2022-05-07 20:08:51 +05:30
2022-06-21 17:19:12 +05:30
expect(trackingSpy).toHaveBeenCalledWith(
'workItems:show',
'viewed_work_item_from_modal',
{
category: 'workItems:show',
label: 'work_item_view',
property: 'type_task',
},
);
});
2022-05-07 20:08:51 +05:30
});
2022-06-21 17:19:12 +05:30
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);
},
);
2022-05-07 20:08:51 +05:30
});
});
2019-03-02 22:35:43 +05:30
});
2017-09-10 17:25:29 +05:30
});