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:
Diffstat (limited to 'spec/frontend/issues/show')
-rw-r--r--spec/frontend/issues/show/components/app_spec.js154
-rw-r--r--spec/frontend/issues/show/components/sticky_header_spec.js135
-rw-r--r--spec/frontend/issues/show/components/task_list_item_actions_spec.js52
-rw-r--r--spec/frontend/issues/show/issue_spec.js43
-rw-r--r--spec/frontend/issues/show/mock_data/mock_data.js3
-rw-r--r--spec/frontend/issues/show/store_spec.js39
6 files changed, 202 insertions, 224 deletions
diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js
index de183f94277..8999952c54c 100644
--- a/spec/frontend/issues/show/components/app_spec.js
+++ b/spec/frontend/issues/show/components/app_spec.js
@@ -1,23 +1,14 @@
-import { GlIcon, GlIntersectionObserver } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
-import {
- issuableStatusText,
- STATUS_CLOSED,
- STATUS_OPEN,
- STATUS_REOPENED,
- TYPE_EPIC,
- TYPE_INCIDENT,
- TYPE_ISSUE,
-} from '~/issues/constants';
+import { TYPE_EPIC, TYPE_INCIDENT, TYPE_ISSUE } 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 StickyHeader from '~/issues/show/components/sticky_header.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';
@@ -44,22 +35,15 @@ describe('Issuable output', () => {
let axiosMock;
let wrapper;
- const findStickyHeader = () => wrapper.findByTestId('issue-sticky-header');
- const findLockedBadge = () => wrapper.findByTestId('locked');
- const findConfidentialBadge = () => wrapper.findByTestId('confidential');
- const findHiddenBadge = () => wrapper.findByTestId('hidden');
-
+ const findStickyHeader = () => wrapper.findComponent(StickyHeader);
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 createComponent = ({ props = {}, options = {}, data = {} } = {}) => {
- wrapper = shallowMountExtended(IssuableApp, {
- directives: {
- GlTooltip: createMockDirective('gl-tooltip'),
- },
+ const createComponent = ({ props = {}, options = {} } = {}) => {
+ wrapper = shallowMount(IssuableApp, {
propsData: { ...appProps, ...props },
provide: {
fullPath: 'gitlab-org/incidents',
@@ -69,11 +53,6 @@ describe('Issuable output', () => {
HighlightBar: true,
IncidentTabs: true,
},
- data() {
- return {
- ...data,
- };
- },
...options,
});
@@ -81,13 +60,6 @@ describe('Issuable output', () => {
return waitForPromises();
};
- const createComponentAndScroll = async (props) => {
- await createComponent({ props });
- global.pageYOffset = 100;
- wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear');
- await nextTick();
- };
-
const emitHubEvent = (event) => {
eventHub.$emit(event);
return waitForPromises();
@@ -332,104 +304,36 @@ describe('Issuable output', () => {
describe('when title is in view', () => {
it('is not shown', async () => {
await createComponent();
- wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear');
- expect(findStickyHeader().exists()).toBe(false);
+ wrapper.findComponent(StickyHeader).vm.$emit('show');
+
+ expect(findStickyHeader().props('show')).toBe(false);
});
});
- describe('when title is not in view', () => {
- it.each([TYPE_INCIDENT, TYPE_ISSUE, TYPE_EPIC])(
- 'shows with title when issuableType="%s"',
- async (issuableType) => {
- await createComponentAndScroll({ issuableType });
-
- expect(findStickyHeader().text()).toContain('this is a title');
- },
- );
-
- it.each`
- issuableType | issuableStatus | statusIcon
- ${TYPE_INCIDENT} | ${STATUS_OPEN} | ${'issues'}
- ${TYPE_INCIDENT} | ${STATUS_CLOSED} | ${'issue-closed'}
- ${TYPE_ISSUE} | ${STATUS_OPEN} | ${'issues'}
- ${TYPE_ISSUE} | ${STATUS_CLOSED} | ${'issue-closed'}
- ${TYPE_EPIC} | ${STATUS_OPEN} | ${'epic'}
- ${TYPE_EPIC} | ${STATUS_CLOSED} | ${'epic-closed'}
- `(
- 'shows with state icon "$statusIcon" for $issuableType when status is $issuableStatus',
- async ({ issuableType, issuableStatus, statusIcon }) => {
- await createComponentAndScroll({ issuableType, issuableStatus });
-
- expect(findStickyHeader().findComponent(GlIcon).props('name')).toBe(statusIcon);
- },
- );
-
- it.each`
- title | issuableStatus
- ${'shows with Open when status is opened'} | ${STATUS_OPEN}
- ${'shows with Closed when status is closed'} | ${STATUS_CLOSED}
- ${'shows with Open when status is reopened'} | ${STATUS_REOPENED}
- `('$title', async ({ issuableStatus }) => {
- await createComponentAndScroll({ issuableStatus });
-
- expect(findStickyHeader().text()).toContain(issuableStatusText[issuableStatus]);
- });
+ describe.each([TYPE_INCIDENT, TYPE_ISSUE, TYPE_EPIC])(
+ 'when title is not in view',
+ (issuableType) => {
+ beforeEach(async () => {
+ await createComponent({ props: { issuableType } });
- it.each`
- title | isConfidential
- ${'does not show confidential badge when issue is not confidential'} | ${false}
- ${'shows confidential badge when issue is confidential'} | ${true}
- `('$title', async ({ isConfidential }) => {
- await createComponentAndScroll({ isConfidential });
- const confidentialEl = findConfidentialBadge();
-
- expect(confidentialEl.exists()).toBe(isConfidential);
-
- if (isConfidential) {
- expect(confidentialEl.props()).toMatchObject({
- workspaceType: 'project',
- issuableType: 'issue',
- });
- }
- });
+ global.pageYOffset = 100;
+ wrapper.findComponent(StickyHeader).vm.$emit('show');
+ await nextTick();
+ });
- it.each`
- title | isLocked
- ${'does not show locked badge when issue is not locked'} | ${false}
- ${'shows locked badge when issue is locked'} | ${true}
- `('$title', async ({ isLocked }) => {
- await createComponentAndScroll({ isLocked });
- const lockedBadge = findLockedBadge();
-
- expect(lockedBadge.exists()).toBe(isLocked);
-
- if (isLocked) {
- expect(lockedBadge.attributes('title')).toBe(
- 'This issue is locked. Only project members can comment.',
- );
- expect(getBinding(lockedBadge.element, 'gl-tooltip')).not.toBeUndefined();
- }
- });
+ it(`shows when issuableType=${issuableType}`, () => {
+ expect(findStickyHeader().props('show')).toBe(true);
+ });
- it.each`
- title | isHidden
- ${'does not show hidden badge when issue is not hidden'} | ${false}
- ${'shows hidden badge when issue is hidden'} | ${true}
- `('$title', async ({ isHidden }) => {
- await createComponentAndScroll({ isHidden });
- const hiddenBadge = findHiddenBadge();
-
- expect(hiddenBadge.exists()).toBe(isHidden);
-
- if (isHidden) {
- expect(hiddenBadge.attributes('title')).toBe(
- 'This issue is hidden because its author has been banned',
- );
- expect(getBinding(hiddenBadge.element, 'gl-tooltip')).not.toBeUndefined();
- }
- });
- });
+ it('hides again when title is back in view', async () => {
+ wrapper.findComponent(StickyHeader).vm.$emit('hide');
+ await nextTick();
+
+ expect(findStickyHeader().props('show')).toBe(false);
+ });
+ },
+ );
});
describe('Composable description component', () => {
diff --git a/spec/frontend/issues/show/components/sticky_header_spec.js b/spec/frontend/issues/show/components/sticky_header_spec.js
new file mode 100644
index 00000000000..0c54ae45e70
--- /dev/null
+++ b/spec/frontend/issues/show/components/sticky_header_spec.js
@@ -0,0 +1,135 @@
+import { GlIcon } from '@gitlab/ui';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import {
+ issuableStatusText,
+ STATUS_CLOSED,
+ STATUS_OPEN,
+ STATUS_REOPENED,
+ TYPE_EPIC,
+ TYPE_INCIDENT,
+ TYPE_ISSUE,
+} from '~/issues/constants';
+import StickyHeader from '~/issues/show/components/sticky_header.vue';
+import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
+
+describe('StickyHeader component', () => {
+ let wrapper;
+
+ const findConfidentialBadge = () => wrapper.findComponent(ConfidentialityBadge);
+ const findHiddenBadge = () => wrapper.findByTestId('hidden');
+ const findLockedBadge = () => wrapper.findByTestId('locked');
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(StickyHeader, {
+ directives: {
+ GlTooltip: createMockDirective('gl-tooltip'),
+ },
+ propsData: {
+ issuableStatus: STATUS_OPEN,
+ issuableType: TYPE_ISSUE,
+ show: true,
+ title: 'A sticky issue',
+ titleHtml: '',
+ ...props,
+ },
+ });
+ };
+
+ it.each`
+ issuableType | issuableStatus | statusIcon
+ ${TYPE_INCIDENT} | ${STATUS_OPEN} | ${'issues'}
+ ${TYPE_INCIDENT} | ${STATUS_CLOSED} | ${'issue-closed'}
+ ${TYPE_ISSUE} | ${STATUS_OPEN} | ${'issues'}
+ ${TYPE_ISSUE} | ${STATUS_CLOSED} | ${'issue-closed'}
+ ${TYPE_EPIC} | ${STATUS_OPEN} | ${'epic'}
+ ${TYPE_EPIC} | ${STATUS_CLOSED} | ${'epic-closed'}
+ `(
+ 'shows with state icon "$statusIcon" for $issuableType when status is $issuableStatus',
+ ({ issuableType, issuableStatus, statusIcon }) => {
+ createComponent({ issuableType, issuableStatus });
+
+ expect(wrapper.findComponent(GlIcon).props('name')).toBe(statusIcon);
+ },
+ );
+
+ it.each`
+ title | issuableStatus
+ ${'shows with Open when status is opened'} | ${STATUS_OPEN}
+ ${'shows with Closed when status is closed'} | ${STATUS_CLOSED}
+ ${'shows with Open when status is reopened'} | ${STATUS_REOPENED}
+ `('$title', ({ issuableStatus }) => {
+ createComponent({ issuableStatus });
+
+ expect(wrapper.text()).toContain(issuableStatusText[issuableStatus]);
+ });
+
+ it.each`
+ title | isConfidential
+ ${'does not show confidential badge when issue is not confidential'} | ${false}
+ ${'shows confidential badge when issue is confidential'} | ${true}
+ `('$title', ({ isConfidential }) => {
+ createComponent({ isConfidential });
+ const confidentialBadge = findConfidentialBadge();
+
+ expect(confidentialBadge.exists()).toBe(isConfidential);
+
+ if (isConfidential) {
+ expect(confidentialBadge.props()).toMatchObject({
+ workspaceType: 'project',
+ issuableType: 'issue',
+ });
+ }
+ });
+
+ it.each`
+ title | isLocked
+ ${'does not show locked badge when issue is not locked'} | ${false}
+ ${'shows locked badge when issue is locked'} | ${true}
+ `('$title', ({ isLocked }) => {
+ createComponent({ isLocked });
+ const lockedBadge = findLockedBadge();
+
+ expect(lockedBadge.exists()).toBe(isLocked);
+
+ if (isLocked) {
+ expect(lockedBadge.attributes('title')).toBe(
+ 'This issue is locked. Only project members can comment.',
+ );
+ expect(getBinding(lockedBadge.element, 'gl-tooltip')).not.toBeUndefined();
+ }
+ });
+
+ it.each`
+ title | isHidden
+ ${'does not show hidden badge when issue is not hidden'} | ${false}
+ ${'shows hidden badge when issue is hidden'} | ${true}
+ `('$title', ({ isHidden }) => {
+ createComponent({ isHidden });
+ const hiddenBadge = findHiddenBadge();
+
+ expect(hiddenBadge.exists()).toBe(isHidden);
+
+ if (isHidden) {
+ expect(hiddenBadge.attributes('title')).toBe(
+ 'This issue is hidden because its author has been banned',
+ );
+ expect(getBinding(hiddenBadge.element, 'gl-tooltip')).not.toBeUndefined();
+ }
+ });
+
+ it('shows with title', () => {
+ createComponent();
+ const title = wrapper.find('a');
+
+ expect(title.text()).toContain('A sticky issue');
+ expect(title.attributes('href')).toBe('#top');
+ });
+
+ it('shows title containing markup', () => {
+ const titleHtml = '<b>A sticky issue</b>';
+ createComponent({ titleHtml });
+
+ expect(wrapper.find('a').html()).toContain(titleHtml);
+ });
+});
diff --git a/spec/frontend/issues/show/components/task_list_item_actions_spec.js b/spec/frontend/issues/show/components/task_list_item_actions_spec.js
index 93cb7b5ae16..b2e57bf49d0 100644
--- a/spec/frontend/issues/show/components/task_list_item_actions_spec.js
+++ b/spec/frontend/issues/show/components/task_list_item_actions_spec.js
@@ -1,5 +1,6 @@
-import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlDisclosureDropdown } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { TYPE_EPIC, TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants';
import TaskListItemActions from '~/issues/show/components/task_list_item_actions.vue';
import eventHub from '~/issues/show/event_hub';
@@ -9,26 +10,24 @@ describe('TaskListItemActions component', () => {
let wrapper;
const findGlDisclosureDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
- const findConvertToTaskItem = () => wrapper.findAllComponents(GlDisclosureDropdownItem).at(0);
- const findDeleteItem = () => wrapper.findAllComponents(GlDisclosureDropdownItem).at(1);
+ const findConvertToTaskItem = () => wrapper.findByTestId('convert');
+ const findDeleteItem = () => wrapper.findByTestId('delete');
- const mountComponent = () => {
+ const mountComponent = ({ issuableType = TYPE_ISSUE } = {}) => {
const li = document.createElement('li');
li.dataset.sourcepos = '3:1-3:10';
li.appendChild(document.createElement('div'));
document.body.appendChild(li);
- wrapper = shallowMount(TaskListItemActions, {
- provide: { canUpdate: true },
+ wrapper = shallowMountExtended(TaskListItemActions, {
+ provide: { canUpdate: true, issuableType },
attachTo: document.querySelector('div'),
});
};
- beforeEach(() => {
+ it('renders dropdown', () => {
mountComponent();
- });
- it('renders dropdown', () => {
expect(findGlDisclosureDropdown().props()).toMatchObject({
category: 'tertiary',
icon: 'ellipsis_v',
@@ -38,15 +37,36 @@ describe('TaskListItemActions component', () => {
});
});
- it('emits event when `Convert to task` dropdown item is clicked', () => {
- findConvertToTaskItem().vm.$emit('action');
+ describe('"Convert to task" dropdown item', () => {
+ describe.each`
+ issuableType | exists
+ ${TYPE_EPIC} | ${false}
+ ${TYPE_INCIDENT} | ${true}
+ ${TYPE_ISSUE} | ${true}
+ `(`when $issuableType`, ({ issuableType, exists }) => {
+ it(`${exists ? 'renders' : 'does not render'}`, () => {
+ mountComponent({ issuableType });
- expect(eventHub.$emit).toHaveBeenCalledWith('convert-task-list-item', '3:1-3:10');
+ expect(findConvertToTaskItem().exists()).toBe(exists);
+ });
+ });
});
- it('emits event when `Delete` dropdown item is clicked', () => {
- findDeleteItem().vm.$emit('action');
+ describe('events', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('emits event when `Convert to task` dropdown item is clicked', () => {
+ findConvertToTaskItem().vm.$emit('action');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('convert-task-list-item', '3:1-3:10');
+ });
- expect(eventHub.$emit).toHaveBeenCalledWith('delete-task-list-item', '3:1-3:10');
+ it('emits event when `Delete` dropdown item is clicked', () => {
+ findDeleteItem().vm.$emit('action');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('delete-task-list-item', '3:1-3:10');
+ });
});
});
diff --git a/spec/frontend/issues/show/issue_spec.js b/spec/frontend/issues/show/issue_spec.js
deleted file mode 100644
index 561035242eb..00000000000
--- a/spec/frontend/issues/show/issue_spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import waitForPromises from 'helpers/wait_for_promises';
-import { initIssueApp } from '~/issues/show';
-import * as parseData from '~/issues/show/utils/parse_data';
-import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
-import createStore from '~/notes/stores';
-import { appProps } from './mock_data/mock_data';
-
-const mock = new MockAdapter(axios);
-mock.onGet().reply(HTTP_STATUS_OK);
-
-jest.mock('~/lib/utils/poll');
-
-const setupHTML = (initialData) => {
- document.body.innerHTML = `<div id="js-issuable-app"></div>`;
- document.getElementById('js-issuable-app').dataset.initial = JSON.stringify(initialData);
-};
-
-describe('Issue show index', () => {
- describe('initIssueApp', () => {
- // quarantine: https://gitlab.com/gitlab-org/gitlab/-/issues/390368
- // eslint-disable-next-line jest/no-disabled-tests
- it.skip('should initialize app with no potential XSS attack', async () => {
- const alertSpy = jest.spyOn(window, 'alert').mockImplementation(() => {});
- const parseDataSpy = jest.spyOn(parseData, 'parseIssuableData');
-
- setupHTML({
- ...appProps,
- initialDescriptionHtml: '<svg onload=window.alert(1)>',
- });
-
- const initialDataEl = document.getElementById('js-issuable-app');
- const issuableData = parseData.parseIssuableData(initialDataEl);
- initIssueApp(issuableData, createStore());
-
- await waitForPromises();
-
- expect(parseDataSpy).toHaveBeenCalled();
- expect(alertSpy).not.toHaveBeenCalled();
- });
- });
-});
diff --git a/spec/frontend/issues/show/mock_data/mock_data.js b/spec/frontend/issues/show/mock_data/mock_data.js
index ed969a08ac5..37aa18ced8d 100644
--- a/spec/frontend/issues/show/mock_data/mock_data.js
+++ b/spec/frontend/issues/show/mock_data/mock_data.js
@@ -1,8 +1,9 @@
import { TEST_HOST } from 'helpers/test_constants';
export const initialRequest = {
- title: '<p>this is a title</p>',
+ title: '<gl-emoji title="party-parrot"></gl-emoji>this is a title',
title_text: 'this is a title',
+ title_html: '<gl-emoji title="party-parrot"></gl-emoji>this is a title',
description: '<p>this is a description!</p>',
description_text: 'this is a description',
task_completion_status: { completed_count: 2, count: 4 },
diff --git a/spec/frontend/issues/show/store_spec.js b/spec/frontend/issues/show/store_spec.js
deleted file mode 100644
index 20d3a6cdaae..00000000000
--- a/spec/frontend/issues/show/store_spec.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import Store from '~/issues/show/stores';
-import updateDescription from '~/issues/show/utils/update_description';
-
-jest.mock('~/issues/show/utils/update_description');
-
-describe('Store', () => {
- let store;
-
- beforeEach(() => {
- store = new Store({
- descriptionHtml: '<p>This is a description</p>',
- });
- });
-
- describe('updateState', () => {
- beforeEach(() => {
- document.body.innerHTML = `
- <div class="detail-page-description content-block">
- <details open>
- <summary>One</summary>
- </details>
- <details>
- <summary>Two</summary>
- </details>
- </div>
- `;
- });
-
- afterEach(() => {
- document.getElementsByTagName('html')[0].innerHTML = '';
- });
-
- it('calls updateDetailsState', () => {
- store.updateState({ description: '' });
-
- expect(updateDescription).toHaveBeenCalledTimes(1);
- });
- });
-});