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/edited_spec.js16
-rw-r--r--spec/frontend/issues/show/components/fields/description_spec.js59
-rw-r--r--spec/frontend/issues/show/components/form_spec.js76
-rw-r--r--spec/frontend/issues/show/components/incidents/incident_tabs_spec.js19
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js24
5 files changed, 151 insertions, 43 deletions
diff --git a/spec/frontend/issues/show/components/edited_spec.js b/spec/frontend/issues/show/components/edited_spec.js
index 8a240c38b5f..aa6e0a9dceb 100644
--- a/spec/frontend/issues/show/components/edited_spec.js
+++ b/spec/frontend/issues/show/components/edited_spec.js
@@ -1,7 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import { getTimeago } from '~/lib/utils/datetime_utility';
import Edited from '~/issues/show/components/edited.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+const timeago = getTimeago();
+
describe('Edited component', () => {
let wrapper;
@@ -9,7 +12,8 @@ describe('Edited component', () => {
const findTimeAgoTooltip = () => wrapper.findComponent(TimeAgoTooltip);
const formatText = (text) => text.trim().replace(/\s\s+/g, ' ');
- const mountComponent = (propsData) => shallowMount(Edited, { propsData });
+ const mountComponent = (propsData) => mount(Edited, { propsData });
+ const updatedAt = '2017-05-15T12:31:04.428Z';
afterEach(() => {
wrapper.destroy();
@@ -17,12 +21,12 @@ describe('Edited component', () => {
it('renders an edited at+by string', () => {
wrapper = mountComponent({
- updatedAt: '2017-05-15T12:31:04.428Z',
+ updatedAt,
updatedByName: 'Some User',
updatedByPath: '/some_user',
});
- expect(formatText(wrapper.text())).toBe('Edited by Some User');
+ expect(formatText(wrapper.text())).toBe(`Edited ${timeago.format(updatedAt)} by Some User`);
expect(findAuthorLink().attributes('href')).toBe('/some_user');
expect(findTimeAgoTooltip().exists()).toBe(true);
});
@@ -40,10 +44,10 @@ describe('Edited component', () => {
it('if no updatedByName and updatedByPath is provided, no user element will be rendered', () => {
wrapper = mountComponent({
- updatedAt: '2017-05-15T12:31:04.428Z',
+ updatedAt,
});
- expect(formatText(wrapper.text())).toBe('Edited');
+ expect(formatText(wrapper.text())).toBe(`Edited ${timeago.format(updatedAt)}`);
expect(findAuthorLink().exists()).toBe(false);
expect(findTimeAgoTooltip().exists()).toBe(true);
});
diff --git a/spec/frontend/issues/show/components/fields/description_spec.js b/spec/frontend/issues/show/components/fields/description_spec.js
index 61433607a2b..cd4d422583b 100644
--- a/spec/frontend/issues/show/components/fields/description_spec.js
+++ b/spec/frontend/issues/show/components/fields/description_spec.js
@@ -2,13 +2,15 @@ import { shallowMount } from '@vue/test-utils';
import DescriptionField from '~/issues/show/components/fields/description.vue';
import eventHub from '~/issues/show/event_hub';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
describe('Description field component', () => {
let wrapper;
const findTextarea = () => wrapper.findComponent({ ref: 'textarea' });
+ const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor);
- const mountComponent = (description = 'test') =>
+ const mountComponent = ({ description = 'test', contentEditorOnIssues = false } = {}) =>
shallowMount(DescriptionField, {
attachTo: document.body,
propsData: {
@@ -17,6 +19,11 @@ describe('Description field component', () => {
quickActionsDocsPath: '/',
value: description,
},
+ provide: {
+ glFeatures: {
+ contentEditorOnIssues,
+ },
+ },
stubs: {
MarkdownField,
},
@@ -40,7 +47,7 @@ describe('Description field component', () => {
it('renders markdown field with a markdown description', () => {
const markdown = '**test**';
- wrapper = mountComponent(markdown);
+ wrapper = mountComponent({ description: markdown });
expect(findTextarea().element.value).toBe(markdown);
});
@@ -66,4 +73,52 @@ describe('Description field component', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('update.issuable');
});
+
+ describe('when contentEditorOnIssues feature flag is on', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({ contentEditorOnIssues: true });
+ });
+
+ it('uses the MarkdownEditor component to edit markdown', () => {
+ expect(findMarkdownEditor().props()).toEqual(
+ expect.objectContaining({
+ value: 'test',
+ renderMarkdownPath: '/',
+ markdownDocsPath: '/',
+ quickActionsDocsPath: expect.any(String),
+ initOnAutofocus: true,
+ supportsQuickActions: true,
+ enableAutocomplete: true,
+ }),
+ );
+ });
+
+ it('triggers update with meta+enter', () => {
+ findMarkdownEditor().vm.$emit('keydown', {
+ type: 'keydown',
+ keyCode: 13,
+ metaKey: true,
+ });
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('update.issuable');
+ });
+
+ it('triggers update with ctrl+enter', () => {
+ findMarkdownEditor().vm.$emit('keydown', {
+ type: 'keydown',
+ keyCode: 13,
+ ctrlKey: true,
+ });
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('update.issuable');
+ });
+
+ it('emits input event when MarkdownEditor emits input event', () => {
+ const markdown = 'markdown';
+
+ findMarkdownEditor().vm.$emit('input', markdown);
+
+ expect(wrapper.emitted('input')).toEqual([[markdown]]);
+ });
+ });
});
diff --git a/spec/frontend/issues/show/components/form_spec.js b/spec/frontend/issues/show/components/form_spec.js
index 5c0fe991b22..aedb974cbd0 100644
--- a/spec/frontend/issues/show/components/form_spec.js
+++ b/spec/frontend/issues/show/components/form_spec.js
@@ -1,14 +1,16 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
-import Autosave from '~/autosave';
+import { getDraft, updateDraft, clearDraft, getLockVersion } from '~/lib/utils/autosave';
import DescriptionTemplate from '~/issues/show/components/fields/description_template.vue';
+import IssuableTitleField from '~/issues/show/components/fields/title.vue';
+import DescriptionField from '~/issues/show/components/fields/description.vue';
import IssueTypeField from '~/issues/show/components/fields/type.vue';
import formComponent from '~/issues/show/components/form.vue';
import LockedWarning from '~/issues/show/components/locked_warning.vue';
import eventHub from '~/issues/show/event_hub';
-jest.mock('~/autosave');
+jest.mock('~/lib/utils/autosave');
describe('Inline edit form component', () => {
let wrapper;
@@ -38,9 +40,14 @@ describe('Inline edit form component', () => {
...defaultProps,
...props,
},
+ stubs: {
+ DescriptionField,
+ },
});
};
+ const findTitleField = () => wrapper.findComponent(IssuableTitleField);
+ const findDescriptionField = () => wrapper.findComponent(DescriptionField);
const findDescriptionTemplate = () => wrapper.findComponent(DescriptionTemplate);
const findIssuableTypeField = () => wrapper.findComponent(IssueTypeField);
const findLockedWarning = () => wrapper.findComponent(LockedWarning);
@@ -108,16 +115,34 @@ describe('Inline edit form component', () => {
});
describe('autosave', () => {
- let spy;
-
beforeEach(() => {
- spy = jest.spyOn(Autosave.prototype, 'reset');
+ getDraft.mockImplementation((autosaveKey) => {
+ return autosaveKey[autosaveKey.length - 1];
+ });
});
- it('initialized Autosave on mount', () => {
+ it('initializes title and description fields with saved drafts', () => {
createComponent();
- expect(Autosave).toHaveBeenCalledTimes(2);
+ expect(findTitleField().props().value).toBe('title');
+ expect(findDescriptionField().props().value).toBe('description');
+ });
+
+ it('updates local storage drafts when title and description change', () => {
+ const updatedTitle = 'updated title';
+ const updatedDescription = 'updated description';
+
+ createComponent();
+
+ findTitleField().vm.$emit('input', updatedTitle);
+ findDescriptionField().vm.$emit('input', updatedDescription);
+
+ expect(updateDraft).toHaveBeenCalledWith(expect.any(Array), updatedTitle);
+ expect(updateDraft).toHaveBeenCalledWith(
+ expect.any(Array),
+ updatedDescription,
+ defaultProps.formState.lock_version,
+ );
});
it('calls reset on autosave when eventHub emits appropriate events', () => {
@@ -125,33 +150,60 @@ describe('Inline edit form component', () => {
eventHub.$emit('close.form');
- expect(spy).toHaveBeenCalledTimes(2);
+ expect(clearDraft).toHaveBeenCalledTimes(2);
eventHub.$emit('delete.issuable');
- expect(spy).toHaveBeenCalledTimes(4);
+ expect(clearDraft).toHaveBeenCalledTimes(4);
eventHub.$emit('update.issuable');
- expect(spy).toHaveBeenCalledTimes(6);
+ expect(clearDraft).toHaveBeenCalledTimes(6);
});
describe('outdated description', () => {
+ const clientSideMockVersion = 'lock version from local storage';
+ const serverSideMockVersion = 'lock version from server';
+
+ const mockGetLockVersion = () => getLockVersion.mockResolvedValue(clientSideMockVersion);
+
it('does not show warning if lock version from server is the same as the local lock version', () => {
createComponent();
expect(findAlert().exists()).toBe(false);
});
it('shows warning if lock version from server differs than the local lock version', async () => {
- Autosave.prototype.getSavedLockVersion.mockResolvedValue('lock version from local storage');
+ mockGetLockVersion();
createComponent({
- formState: { ...defaultProps.formState, lock_version: 'lock version from server' },
+ formState: { ...defaultProps.formState, lock_version: serverSideMockVersion },
});
await nextTick();
expect(findAlert().exists()).toBe(true);
});
+
+ describe('when saved draft is discarded', () => {
+ beforeEach(async () => {
+ mockGetLockVersion();
+
+ createComponent({
+ formState: { ...defaultProps.formState, lock_version: serverSideMockVersion },
+ });
+
+ await nextTick();
+
+ findAlert().vm.$emit('secondaryAction');
+ });
+
+ it('hides the warning alert', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('clears the description draft', () => {
+ expect(clearDraft).toHaveBeenCalledWith(expect.any(Array));
+ });
+ });
});
});
});
diff --git a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js
index d92aeabba0f..458c1c3f858 100644
--- a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js
+++ b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js
@@ -5,7 +5,6 @@ import { trackIncidentDetailsViewsOptions } from '~/incidents/constants';
import DescriptionComponent from '~/issues/show/components/description.vue';
import HighlightBar from '~/issues/show/components/incidents/highlight_bar.vue';
import IncidentTabs from '~/issues/show/components/incidents/incident_tabs.vue';
-import TimelineTab from '~/issues/show/components/incidents/timeline_events_tab.vue';
import INVALID_URL from '~/lib/utils/invalid_url';
import Tracking from '~/tracking';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
@@ -38,7 +37,6 @@ describe('Incident Tabs component', () => {
projectId: '',
issuableId: '',
uploadMetricsFeatureAvailable: true,
- glFeatures: { incidentTimeline: true },
},
data() {
return { alert: mockAlert, ...data };
@@ -67,7 +65,6 @@ describe('Incident Tabs component', () => {
const findAlertDetailsComponent = () => wrapper.findComponent(AlertDetailsTable);
const findDescriptionComponent = () => wrapper.findComponent(DescriptionComponent);
const findHighlightBarComponent = () => wrapper.findComponent(HighlightBar);
- const findTimelineTab = () => wrapper.findComponent(TimelineTab);
describe('empty state', () => {
beforeEach(() => {
@@ -128,20 +125,4 @@ describe('Incident Tabs component', () => {
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
});
-
- describe('incident timeline tab', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('renders the timeline tab when feature flag is enabled', () => {
- expect(findTimelineTab().exists()).toBe(true);
- });
-
- it('does not render timeline tab when feature flag is disabled', () => {
- mountComponent({}, { provide: { glFeatures: { incidentTimeline: false } } });
-
- expect(findTimelineTab().exists()).toBe(false);
- });
- });
});
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
index 7f086a276f7..2e7449974e5 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
@@ -22,12 +22,15 @@ describe('Timeline events form', () => {
useFakeDate(fakeDate);
let wrapper;
- const mountComponent = ({ mountMethod = shallowMountExtended }) => {
+ const mountComponent = ({ mountMethod = shallowMountExtended } = {}) => {
wrapper = mountMethod(TimelineEventsForm, {
propsData: {
showSaveAndAdd: true,
isEventProcessed: false,
},
+ stubs: {
+ GlButton: true,
+ },
});
};
@@ -48,17 +51,18 @@ describe('Timeline events form', () => {
findHourInput().setValue(5);
findMinuteInput().setValue(45);
};
+ const findTextarea = () => wrapper.findByTestId('input-note');
const submitForm = async () => {
- findSubmitButton().trigger('click');
+ findSubmitButton().vm.$emit('click');
await waitForPromises();
};
const submitFormAndAddAnother = async () => {
- findSubmitAndAddButton().trigger('click');
+ findSubmitAndAddButton().vm.$emit('click');
await waitForPromises();
};
const cancelForm = async () => {
- findCancelButton().trigger('click');
+ findCancelButton().vm.$emit('click');
await waitForPromises();
};
@@ -118,5 +122,17 @@ describe('Timeline events form', () => {
expect(findHourInput().element.value).toBe('0');
expect(findMinuteInput().element.value).toBe('0');
});
+
+ it('should disable the save buttons when event content does not exist', async () => {
+ expect(findSubmitButton().props('disabled')).toBe(true);
+ expect(findSubmitAndAddButton().props('disabled')).toBe(true);
+ });
+
+ it('should enable the save buttons when event content exists', async () => {
+ await findTextarea().setValue('hello');
+
+ expect(findSubmitButton().props('disabled')).toBe(false);
+ expect(findSubmitAndAddButton().props('disabled')).toBe(false);
+ });
});
});