summaryrefslogtreecommitdiff
path: root/spec/frontend/issue_show
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/issue_show')
-rw-r--r--spec/frontend/issue_show/components/app_spec.js41
-rw-r--r--spec/frontend/issue_show/components/description_spec.js2
-rw-r--r--spec/frontend/issue_show/components/edit_actions_spec.js180
-rw-r--r--spec/frontend/issue_show/components/fields/type_spec.js84
-rw-r--r--spec/frontend/issue_show/components/form_spec.js17
-rw-r--r--spec/frontend/issue_show/components/incidents/incident_tabs_spec.js2
-rw-r--r--spec/frontend/issue_show/issue_spec.js2
-rw-r--r--spec/frontend/issue_show/mock_data/apollo_mock.js9
-rw-r--r--spec/frontend/issue_show/mock_data/mock_data.js (renamed from spec/frontend/issue_show/mock_data.js)1
9 files changed, 252 insertions, 86 deletions
diff --git a/spec/frontend/issue_show/components/app_spec.js b/spec/frontend/issue_show/components/app_spec.js
index b8860e93a22..4c06f2dca1b 100644
--- a/spec/frontend/issue_show/components/app_spec.js
+++ b/spec/frontend/issue_show/components/app_spec.js
@@ -1,6 +1,7 @@
import { GlIntersectionObserver } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import '~/behaviors/markdown/render_gfm';
import IssuableApp from '~/issue_show/components/app.vue';
@@ -17,7 +18,7 @@ import {
publishedIncidentUrl,
secondRequest,
zoomMeetingUrl,
-} from '../mock_data';
+} from '../mock_data/mock_data';
function formatText(text) {
return text.trim().replace(/\s\s+/g, ' ');
@@ -36,12 +37,11 @@ describe('Issuable output', () => {
let wrapper;
const findStickyHeader = () => wrapper.find('[data-testid="issue-sticky-header"]');
-
const findLockedBadge = () => wrapper.find('[data-testid="locked"]');
-
const findConfidentialBadge = () => wrapper.find('[data-testid="confidential"]');
+ const findAlert = () => wrapper.find('.alert');
- const mountComponent = (props = {}, options = {}) => {
+ const mountComponent = (props = {}, options = {}, data = {}) => {
wrapper = mount(IssuableApp, {
propsData: { ...appProps, ...props },
provide: {
@@ -53,6 +53,11 @@ describe('Issuable output', () => {
HighlightBar: true,
IncidentTabs: true,
},
+ data() {
+ return {
+ ...data,
+ };
+ },
...options,
});
};
@@ -91,10 +96,8 @@ describe('Issuable output', () => {
afterEach(() => {
mock.restore();
realtimeRequestCount = 0;
-
wrapper.vm.poll.stop();
wrapper.destroy();
- wrapper = null;
});
it('should render a title/description/edited and update title/description/edited on update', () => {
@@ -115,7 +118,7 @@ describe('Issuable output', () => {
expect(formatText(editedText.text())).toMatch(/Edited[\s\S]+?by Some User/);
expect(editedText.find('.author-link').attributes('href')).toMatch(/\/some_user$/);
expect(editedText.find('time').text()).toBeTruthy();
- expect(wrapper.vm.state.lock_version).toEqual(1);
+ expect(wrapper.vm.state.lock_version).toBe(initialRequest.lock_version);
})
.then(() => {
wrapper.vm.poll.makeRequest();
@@ -133,7 +136,9 @@ describe('Issuable output', () => {
expect(editedText.find('.author-link').attributes('href')).toMatch(/\/other_user$/);
expect(editedText.find('time').text()).toBeTruthy();
- expect(wrapper.vm.state.lock_version).toEqual(2);
+ // As the lock_version value does not differ from the server,
+ // we should not see an alert
+ expect(findAlert().exists()).toBe(false);
});
});
@@ -172,7 +177,7 @@ describe('Issuable output', () => {
${'zoomMeetingUrl'} | ${zoomMeetingUrl}
${'publishedIncidentUrl'} | ${publishedIncidentUrl}
`('sets the $prop correctly on underlying pinned links', ({ prop, value }) => {
- expect(wrapper.vm[prop]).toEqual(value);
+ expect(wrapper.vm[prop]).toBe(value);
expect(wrapper.find(`[data-testid="${prop}"]`).attributes('href')).toBe(value);
});
});
@@ -374,9 +379,9 @@ describe('Issuable output', () => {
});
})
.then(() => {
- expect(wrapper.vm.formState.lockedWarningVisible).toEqual(true);
- expect(wrapper.vm.formState.lock_version).toEqual(1);
- expect(wrapper.find('.alert').exists()).toBe(true);
+ expect(wrapper.vm.formState.lockedWarningVisible).toBe(true);
+ expect(wrapper.vm.formState.lock_version).toBe(1);
+ expect(findAlert().exists()).toBe(true);
});
});
});
@@ -530,7 +535,7 @@ describe('Issuable output', () => {
`('$title', async ({ state }) => {
wrapper.setProps({ issuableStatus: state });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findStickyHeader().text()).toContain(IssuableStatusText[state]);
});
@@ -542,7 +547,7 @@ describe('Issuable output', () => {
`('$title', async ({ isConfidential }) => {
wrapper.setProps({ isConfidential });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findConfidentialBadge().exists()).toBe(isConfidential);
});
@@ -554,7 +559,7 @@ describe('Issuable output', () => {
`('$title', async ({ isLocked }) => {
wrapper.setProps({ isLocked });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findLockedBadge().exists()).toBe(isLocked);
});
@@ -562,9 +567,9 @@ describe('Issuable output', () => {
});
describe('Composable description component', () => {
- const findIncidentTabs = () => wrapper.find(IncidentTabs);
- const findDescriptionComponent = () => wrapper.find(DescriptionComponent);
- const findPinnedLinks = () => wrapper.find(PinnedLinks);
+ const findIncidentTabs = () => wrapper.findComponent(IncidentTabs);
+ const findDescriptionComponent = () => wrapper.findComponent(DescriptionComponent);
+ const findPinnedLinks = () => wrapper.findComponent(PinnedLinks);
const borderClass = 'gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-mb-6';
describe('when using description component', () => {
diff --git a/spec/frontend/issue_show/components/description_spec.js b/spec/frontend/issue_show/components/description_spec.js
index 70c04280675..cdf06ecc31f 100644
--- a/spec/frontend/issue_show/components/description_spec.js
+++ b/spec/frontend/issue_show/components/description_spec.js
@@ -5,7 +5,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import mountComponent from 'helpers/vue_mount_component_helper';
import Description from '~/issue_show/components/description.vue';
import TaskList from '~/task_list';
-import { descriptionProps as props } from '../mock_data';
+import { descriptionProps as props } from '../mock_data/mock_data';
jest.mock('~/task_list');
diff --git a/spec/frontend/issue_show/components/edit_actions_spec.js b/spec/frontend/issue_show/components/edit_actions_spec.js
index 54707879f63..50c27cb5bda 100644
--- a/spec/frontend/issue_show/components/edit_actions_spec.js
+++ b/spec/frontend/issue_show/components/edit_actions_spec.js
@@ -1,113 +1,163 @@
-import Vue from 'vue';
-import editActions from '~/issue_show/components/edit_actions.vue';
+import { GlButton, GlModal } from '@gitlab/ui';
+import { createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import IssuableEditActions from '~/issue_show/components/edit_actions.vue';
import eventHub from '~/issue_show/event_hub';
-import Store from '~/issue_show/stores';
-describe('Edit Actions components', () => {
- let vm;
+import {
+ getIssueStateQueryResponse,
+ updateIssueStateQueryResponse,
+} from '../mock_data/apollo_mock';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('Edit Actions component', () => {
+ let wrapper;
+ let fakeApollo;
+ let mockIssueStateData;
+
+ const mockResolvers = {
+ Query: {
+ issueState() {
+ return {
+ __typename: 'IssueState',
+ rawData: mockIssueStateData(),
+ };
+ },
+ },
+ };
- beforeEach((done) => {
- const Component = Vue.extend(editActions);
- const store = new Store({
- titleHtml: '',
- descriptionHtml: '',
- issuableRef: '',
- });
- store.formState.title = 'test';
+ const modalId = 'delete-issuable-modal-1';
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ const createComponent = ({ props, data } = {}) => {
+ fakeApollo = createMockApollo([], mockResolvers);
- vm = new Component({
+ wrapper = shallowMountExtended(IssuableEditActions, {
+ apolloProvider: fakeApollo,
propsData: {
+ formState: {
+ title: 'GitLab Issue',
+ },
canDestroy: true,
- formState: store.formState,
issuableType: 'issue',
+ ...props,
},
- }).$mount();
+ data() {
+ return {
+ issueState: {},
+ modalId,
+ ...data,
+ };
+ },
+ });
+ };
- Vue.nextTick(done);
- });
+ async function deleteIssuable(localWrapper) {
+ localWrapper.findComponent(GlModal).vm.$emit('primary');
+ }
- it('renders all buttons as enabled', () => {
- expect(vm.$el.querySelectorAll('.disabled').length).toBe(0);
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findEditButtons = () => wrapper.findAllComponents(GlButton);
+ const findDeleteButton = () => wrapper.findByTestId('issuable-delete-button');
+ const findSaveButton = () => wrapper.findByTestId('issuable-save-button');
+ const findCancelButton = () => wrapper.findByTestId('issuable-cancel-button');
- expect(vm.$el.querySelectorAll('[disabled]').length).toBe(0);
+ beforeEach(() => {
+ mockIssueStateData = jest.fn();
+ createComponent();
});
- it('does not render delete button if canUpdate is false', (done) => {
- vm.canDestroy = false;
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn-danger')).toBeNull();
+ afterEach(() => {
+ wrapper.destroy();
+ });
- done();
+ it('renders all buttons as enabled', () => {
+ const buttons = findEditButtons().wrappers;
+ buttons.forEach((button) => {
+ expect(button.attributes('disabled')).toBeFalsy();
});
});
- it('disables submit button when title is blank', (done) => {
- vm.formState.title = '';
+ it('does not render the delete button if canDestroy is false', () => {
+ createComponent({ props: { canDestroy: false } });
+ expect(findDeleteButton().exists()).toBe(false);
+ });
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn-confirm').getAttribute('disabled')).toBe('disabled');
+ it('disables save button when title is blank', () => {
+ createComponent({ props: { formState: { title: '', issue_type: '' } } });
- done();
- });
+ expect(findSaveButton().attributes('disabled')).toBe('true');
});
- it('should not show delete button if showDeleteButton is false', (done) => {
- vm.showDeleteButton = false;
+ it('does not render the delete button if showDeleteButton is false', () => {
+ createComponent({ props: { showDeleteButton: false } });
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn-danger')).toBeNull();
- done();
- });
+ expect(findDeleteButton().exists()).toBe(false);
});
describe('updateIssuable', () => {
- it('sends update.issauble event when clicking save button', () => {
- vm.$el.querySelector('.btn-confirm').click();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('update.issuable');
+ beforeEach(() => {
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
});
- it('disabled button after clicking save button', (done) => {
- vm.$el.querySelector('.btn-confirm').click();
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn-confirm').getAttribute('disabled')).toBe('disabled');
+ it('sends update.issauble event when clicking save button', () => {
+ findSaveButton().vm.$emit('click', { preventDefault: jest.fn() });
- done();
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('update.issuable');
});
});
describe('closeForm', () => {
+ beforeEach(() => {
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ });
+
it('emits close.form when clicking cancel', () => {
- vm.$el.querySelector('.btn-default').click();
+ findCancelButton().vm.$emit('click');
expect(eventHub.$emit).toHaveBeenCalledWith('close.form');
});
});
- describe('deleteIssuable', () => {
- it('sends delete.issuable event when clicking save button', () => {
- jest.spyOn(window, 'confirm').mockReturnValue(true);
- vm.$el.querySelector('.btn-danger').click();
+ describe('renders create modal with the correct information', () => {
+ it('renders correct modal id', () => {
+ expect(findModal().attributes('modalid')).toBe(modalId);
+ });
+ });
- expect(eventHub.$emit).toHaveBeenCalledWith('delete.issuable', { destroy_confirm: true });
+ describe('deleteIssuable', () => {
+ beforeEach(() => {
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
});
- it('does no actions when confirm is false', (done) => {
- jest.spyOn(window, 'confirm').mockReturnValue(false);
- vm.$el.querySelector('.btn-danger').click();
+ it('does not send the `delete.issuable` event when clicking delete button', () => {
+ findDeleteButton().vm.$emit('click');
+ expect(eventHub.$emit).not.toHaveBeenCalled();
+ });
- Vue.nextTick(() => {
- expect(eventHub.$emit).not.toHaveBeenCalledWith('delete.issuable');
+ it('sends the `delete.issuable` event when clicking the delete confirm button', async () => {
+ expect(eventHub.$emit).toHaveBeenCalledTimes(0);
+ await deleteIssuable(wrapper);
+ expect(eventHub.$emit).toHaveBeenCalledWith('delete.issuable', { destroy_confirm: true });
+ expect(eventHub.$emit).toHaveBeenCalledTimes(1);
+ });
+ });
- expect(vm.$el.querySelector('.btn-danger .fa')).toBeNull();
+ describe('with Apollo cache mock', () => {
+ it('renders the right delete button text per apollo cache type', async () => {
+ mockIssueStateData.mockResolvedValue(getIssueStateQueryResponse);
+ await waitForPromises();
+ expect(findDeleteButton().text()).toBe('Delete issue');
+ });
- done();
- });
+ it('should not change the delete button text per apollo cache mutation', async () => {
+ mockIssueStateData.mockResolvedValue(updateIssueStateQueryResponse);
+ await waitForPromises();
+ expect(findDeleteButton().text()).toBe('Delete issue');
});
});
});
diff --git a/spec/frontend/issue_show/components/fields/type_spec.js b/spec/frontend/issue_show/components/fields/type_spec.js
new file mode 100644
index 00000000000..0c8af60d50d
--- /dev/null
+++ b/spec/frontend/issue_show/components/fields/type_spec.js
@@ -0,0 +1,84 @@
+import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import IssueTypeField, { i18n } from '~/issue_show/components/fields/type.vue';
+import { IssuableTypes } from '~/issue_show/constants';
+import {
+ getIssueStateQueryResponse,
+ updateIssueStateQueryResponse,
+} from '../../mock_data/apollo_mock';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('Issue type field component', () => {
+ let wrapper;
+ let fakeApollo;
+ let mockIssueStateData;
+
+ const mockResolvers = {
+ Query: {
+ issueState() {
+ return {
+ __typename: 'IssueState',
+ rawData: mockIssueStateData(),
+ };
+ },
+ },
+ Mutation: {
+ updateIssueState: jest.fn().mockResolvedValue(updateIssueStateQueryResponse),
+ },
+ };
+
+ const findTypeFromGroup = () => wrapper.findComponent(GlFormGroup);
+ const findTypeFromDropDown = () => wrapper.findComponent(GlDropdown);
+ const findTypeFromDropDownItems = () => wrapper.findAllComponents(GlDropdownItem);
+
+ const createComponent = ({ data } = {}) => {
+ fakeApollo = createMockApollo([], mockResolvers);
+
+ wrapper = shallowMount(IssueTypeField, {
+ localVue,
+ apolloProvider: fakeApollo,
+ data() {
+ return {
+ issueState: {},
+ ...data,
+ };
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mockIssueStateData = jest.fn();
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders a form group with the correct label', () => {
+ expect(findTypeFromGroup().attributes('label')).toBe(i18n.label);
+ });
+
+ it('renders a form select with the `issue_type` value', () => {
+ expect(findTypeFromDropDown().attributes('value')).toBe(IssuableTypes.issue);
+ });
+
+ describe('with Apollo cache mock', () => {
+ it('renders the selected issueType', async () => {
+ mockIssueStateData.mockResolvedValue(getIssueStateQueryResponse);
+ await waitForPromises();
+ expect(findTypeFromDropDown().attributes('value')).toBe(IssuableTypes.issue);
+ });
+
+ it('updates the `issue_type` in the apollo cache when the value is changed', async () => {
+ findTypeFromDropDownItems().at(1).vm.$emit('click', IssuableTypes.incident);
+ await wrapper.vm.$nextTick();
+ expect(findTypeFromDropDown().attributes('value')).toBe(IssuableTypes.incident);
+ });
+ });
+});
diff --git a/spec/frontend/issue_show/components/form_spec.js b/spec/frontend/issue_show/components/form_spec.js
index 6d4807c4261..28498cb90ec 100644
--- a/spec/frontend/issue_show/components/form_spec.js
+++ b/spec/frontend/issue_show/components/form_spec.js
@@ -2,6 +2,7 @@ import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Autosave from '~/autosave';
import DescriptionTemplate from '~/issue_show/components/fields/description_template.vue';
+import IssueTypeField from '~/issue_show/components/fields/type.vue';
import formComponent from '~/issue_show/components/form.vue';
import LockedWarning from '~/issue_show/components/locked_warning.vue';
import eventHub from '~/issue_show/event_hub';
@@ -39,6 +40,7 @@ describe('Inline edit form component', () => {
};
const findDescriptionTemplate = () => wrapper.findComponent(DescriptionTemplate);
+ const findIssuableTypeField = () => wrapper.findComponent(IssueTypeField);
const findLockedWarning = () => wrapper.findComponent(LockedWarning);
const findAlert = () => wrapper.findComponent(GlAlert);
@@ -68,6 +70,21 @@ describe('Inline edit form component', () => {
expect(findDescriptionTemplate().exists()).toBe(true);
});
+ it.each`
+ issuableType | value
+ ${'issue'} | ${true}
+ ${'epic'} | ${false}
+ `(
+ 'when `issue_type` is set to "$issuableType" rendering the type select will be "$value"',
+ ({ issuableType, value }) => {
+ createComponent({
+ issuableType,
+ });
+
+ expect(findIssuableTypeField().exists()).toBe(value);
+ },
+ );
+
it('hides locked warning by default', () => {
createComponent();
diff --git a/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js b/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js
index f46b6ba6f54..6b9f5b17e99 100644
--- a/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js
+++ b/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js
@@ -9,7 +9,7 @@ import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue';
import INVALID_URL from '~/lib/utils/invalid_url';
import Tracking from '~/tracking';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
-import { descriptionProps } from '../../mock_data';
+import { descriptionProps } from '../../mock_data/mock_data';
const mockAlert = {
__typename: 'AlertManagementAlert',
diff --git a/spec/frontend/issue_show/issue_spec.js b/spec/frontend/issue_show/issue_spec.js
index 9cb7059dd7f..d043693b863 100644
--- a/spec/frontend/issue_show/issue_spec.js
+++ b/spec/frontend/issue_show/issue_spec.js
@@ -5,7 +5,7 @@ import { initIssuableApp } from '~/issue_show/issue';
import * as parseData from '~/issue_show/utils/parse_data';
import axios from '~/lib/utils/axios_utils';
import createStore from '~/notes/stores';
-import { appProps } from './mock_data';
+import { appProps } from './mock_data/mock_data';
const mock = new MockAdapter(axios);
mock.onGet().reply(200);
diff --git a/spec/frontend/issue_show/mock_data/apollo_mock.js b/spec/frontend/issue_show/mock_data/apollo_mock.js
new file mode 100644
index 00000000000..bfd31e74393
--- /dev/null
+++ b/spec/frontend/issue_show/mock_data/apollo_mock.js
@@ -0,0 +1,9 @@
+export const getIssueStateQueryResponse = {
+ issueType: 'issue',
+ isDirty: false,
+};
+
+export const updateIssueStateQueryResponse = {
+ issueType: 'incident',
+ isDirty: true,
+};
diff --git a/spec/frontend/issue_show/mock_data.js b/spec/frontend/issue_show/mock_data/mock_data.js
index fd08c95b454..a73826954c3 100644
--- a/spec/frontend/issue_show/mock_data.js
+++ b/spec/frontend/issue_show/mock_data/mock_data.js
@@ -48,6 +48,7 @@ export const appProps = {
initialDescriptionHtml: 'test',
initialDescriptionText: 'test',
lockVersion: 1,
+ issueType: 'issue',
markdownPreviewPath: '/',
markdownDocsPath: '/',
projectNamespace: '/',