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-09-20 02:18:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-09-20 02:18:09 +0300
commit6ed4ec3e0b1340f96b7c043ef51d1b33bbe85fde (patch)
treedc4d20fe6064752c0bd323187252c77e0a89144b /spec/frontend/notes
parent9868dae7fc0655bd7ce4a6887d4e6d487690eeed (diff)
Add latest changes from gitlab-org/gitlab@15-4-stable-eev15.4.0-rc42
Diffstat (limited to 'spec/frontend/notes')
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js8
-rw-r--r--spec/frontend/notes/components/discussion_counter_spec.js46
-rw-r--r--spec/frontend/notes/components/discussion_filter_spec.js81
-rw-r--r--spec/frontend/notes/components/discussion_notes_spec.js8
-rw-r--r--spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js2
-rw-r--r--spec/frontend/notes/components/multiline_comment_form_spec.js2
-rw-r--r--spec/frontend/notes/components/note_actions/timeline_event_button_spec.js35
-rw-r--r--spec/frontend/notes/components/note_body_spec.js10
-rw-r--r--spec/frontend/notes/components/note_form_spec.js12
-rw-r--r--spec/frontend/notes/components/note_header_spec.js30
-rw-r--r--spec/frontend/notes/components/noteable_discussion_spec.js2
-rw-r--r--spec/frontend/notes/components/noteable_note_spec.js2
-rw-r--r--spec/frontend/notes/components/sort_discussion_spec.js102
-rw-r--r--spec/frontend/notes/mixins/discussion_navigation_spec.js31
-rw-r--r--spec/frontend/notes/stores/actions_spec.js101
-rw-r--r--spec/frontend/notes/stores/getters_spec.js6
-rw-r--r--spec/frontend/notes/stores/mutation_spec.js2
17 files changed, 294 insertions, 186 deletions
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index 463787c148b..55e4ef42e37 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -586,10 +586,10 @@ describe('issue_comment_form component', () => {
${true}
${false}
`('when checkbox value is `$shouldCheckboxBeChecked`', ({ shouldCheckboxBeChecked }) => {
- it(`sets \`confidential\` to \`${shouldCheckboxBeChecked}\``, async () => {
+ it(`sets \`internal\` to \`${shouldCheckboxBeChecked}\``, async () => {
mountComponent({
mountFunction: mount,
- initialData: { note: 'confidential note' },
+ initialData: { note: 'internal note' },
noteableData: { ...notableDataMockCanUpdateIssuable },
});
@@ -606,7 +606,7 @@ describe('issue_comment_form component', () => {
findCommentButton().trigger('click');
const [providedData] = wrapper.vm.saveNote.mock.calls[0];
- expect(providedData.data.note.confidential).toBe(shouldCheckboxBeChecked);
+ expect(providedData.data.note.internal).toBe(shouldCheckboxBeChecked);
});
});
@@ -679,7 +679,7 @@ describe('issue_comment_form component', () => {
);
});
- it('clicking `add comment now`, should call note endpoint, set `isDraft` false ', () => {
+ it('clicking `add comment now`, should call note endpoint, set `isDraft` false', () => {
mountComponent({ mountFunction: mount, initialData: { note: 'a comment' } });
jest.spyOn(store, 'dispatch').mockResolvedValue();
diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js
index a7e2f1efa09..f4ec7f835bb 100644
--- a/spec/frontend/notes/components/discussion_counter_spec.js
+++ b/spec/frontend/notes/components/discussion_counter_spec.js
@@ -1,5 +1,5 @@
-import { GlButton } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlDropdownItem } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DiscussionCounter from '~/notes/components/discussion_counter.vue';
@@ -45,7 +45,7 @@ describe('DiscussionCounter component', () => {
describe('has no discussions', () => {
it('does not render', () => {
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
expect(wrapper.findComponent({ ref: 'discussionCounter' }).exists()).toBe(false);
});
@@ -55,7 +55,7 @@ describe('DiscussionCounter component', () => {
it('does not render', () => {
store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [{ ...discussionMock, resolvable: false }]);
store.dispatch('updateResolvableDiscussionsCounts');
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
expect(wrapper.findComponent({ ref: 'discussionCounter' }).exists()).toBe(false);
});
@@ -75,7 +75,7 @@ describe('DiscussionCounter component', () => {
it('renders', () => {
updateStore();
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
expect(wrapper.findComponent({ ref: 'discussionCounter' }).exists()).toBe(true);
});
@@ -89,7 +89,7 @@ describe('DiscussionCounter component', () => {
({ blocksMerge, color }) => {
updateStore();
store.state.unresolvedDiscussionsCount = 1;
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge } });
expect(wrapper.find('[data-testid="discussions-counter-text"]').classes()).toContain(color);
},
@@ -97,60 +97,58 @@ describe('DiscussionCounter component', () => {
it.each`
title | resolved | groupLength
- ${'not allResolved'} | ${false} | ${4}
+ ${'not allResolved'} | ${false} | ${2}
${'allResolved'} | ${true} | ${1}
- `('renders correctly if $title', ({ resolved, groupLength }) => {
+ `('renders correctly if $title', async ({ resolved, groupLength }) => {
updateStore({ resolvable: true, resolved });
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ await wrapper.find('.dropdown-toggle').trigger('click');
- expect(wrapper.findAllComponents(GlButton)).toHaveLength(groupLength);
+ expect(wrapper.findAllComponents(GlDropdownItem)).toHaveLength(groupLength);
});
});
describe('toggle all threads button', () => {
let toggleAllButton;
- const updateStoreWithExpanded = (expanded) => {
+ const updateStoreWithExpanded = async (expanded) => {
const discussion = { ...discussionMock, expanded };
store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [discussion]);
store.dispatch('updateResolvableDiscussionsCounts');
- wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
- toggleAllButton = wrapper.find('.toggle-all-discussions-btn');
+ wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
+ await wrapper.find('.dropdown-toggle').trigger('click');
+ toggleAllButton = wrapper.find('[data-testid="toggle-all-discussions-btn"]');
};
afterEach(() => wrapper.destroy());
- it('calls button handler when clicked', () => {
- updateStoreWithExpanded(true);
+ it('calls button handler when clicked', async () => {
+ await updateStoreWithExpanded(true);
- toggleAllButton.vm.$emit('click');
+ toggleAllButton.trigger('click');
expect(setExpandDiscussionsFn).toHaveBeenCalledTimes(1);
});
it('collapses all discussions if expanded', async () => {
- updateStoreWithExpanded(true);
+ await updateStoreWithExpanded(true);
expect(wrapper.vm.allExpanded).toBe(true);
- expect(toggleAllButton.props('icon')).toBe('collapse');
- toggleAllButton.vm.$emit('click');
+ toggleAllButton.trigger('click');
await nextTick();
expect(wrapper.vm.allExpanded).toBe(false);
- expect(toggleAllButton.props('icon')).toBe('expand');
});
it('expands all discussions if collapsed', async () => {
- updateStoreWithExpanded(false);
+ await updateStoreWithExpanded(false);
expect(wrapper.vm.allExpanded).toBe(false);
- expect(toggleAllButton.props('icon')).toBe('expand');
- toggleAllButton.vm.$emit('click');
+ toggleAllButton.trigger('click');
await nextTick();
expect(wrapper.vm.allExpanded).toBe(true);
- expect(toggleAllButton.props('icon')).toBe('collapse');
});
});
});
diff --git a/spec/frontend/notes/components/discussion_filter_spec.js b/spec/frontend/notes/components/discussion_filter_spec.js
index 27206bddbfc..ed9fc47540d 100644
--- a/spec/frontend/notes/components/discussion_filter_spec.js
+++ b/spec/frontend/notes/components/discussion_filter_spec.js
@@ -8,7 +8,14 @@ import createEventHub from '~/helpers/event_hub_factory';
import axios from '~/lib/utils/axios_utils';
import DiscussionFilter from '~/notes/components/discussion_filter.vue';
-import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+import Tracking from '~/tracking';
+import {
+ DISCUSSION_FILTERS_DEFAULT_VALUE,
+ DISCUSSION_FILTER_TYPES,
+ ASC,
+ DESC,
+} from '~/notes/constants';
import notesModule from '~/notes/stores/modules';
import { discussionFiltersMock, discussionMock } from '../mock_data';
@@ -28,6 +35,8 @@ describe('DiscussionFilter component', () => {
const findFilter = (filterType) =>
wrapper.find(`.dropdown-item[data-filter-type="${filterType}"]`);
+ const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
+
const mountComponent = () => {
const discussions = [
{
@@ -68,6 +77,7 @@ describe('DiscussionFilter component', () => {
mock.onGet(DISCUSSION_PATH).reply(200, '');
window.mrTabs = undefined;
wrapper = mountComponent();
+ jest.spyOn(Tracking, 'event');
});
afterEach(() => {
@@ -75,6 +85,65 @@ describe('DiscussionFilter component', () => {
mock.restore();
});
+ describe('default', () => {
+ beforeEach(() => {
+ jest.spyOn(store, 'dispatch').mockImplementation();
+ });
+
+ it('has local storage sync with the correct props', () => {
+ expect(findLocalStorageSync().props('asString')).toBe(true);
+ });
+
+ it('calls setDiscussionSortDirection when update is emitted', () => {
+ findLocalStorageSync().vm.$emit('input', ASC);
+
+ expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { direction: ASC });
+ });
+ });
+
+ describe('when asc', () => {
+ beforeEach(() => {
+ jest.spyOn(store, 'dispatch').mockImplementation();
+ });
+
+ describe('when the dropdown is clicked', () => {
+ it('calls the right actions', () => {
+ wrapper.find('.js-newest-first').vm.$emit('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
+ direction: DESC,
+ });
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
+ property: DESC,
+ });
+ });
+ });
+ });
+
+ describe('when desc', () => {
+ beforeEach(() => {
+ store.state.discussionSortOrder = DESC;
+ jest.spyOn(store, 'dispatch').mockImplementation();
+ });
+
+ describe('when the dropdown item is clicked', () => {
+ it('calls the right actions', () => {
+ wrapper.find('.js-oldest-first').vm.$emit('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
+ direction: ASC,
+ });
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
+ property: ASC,
+ });
+ });
+
+ it('sets is-checked to true on the active button in the dropdown', () => {
+ expect(wrapper.find('.js-newest-first').props('isChecked')).toBe(true);
+ });
+ });
+ });
+
it('renders the all filters', () => {
expect(wrapper.findAll('.discussion-filter-container .dropdown-item').length).toBe(
discussionFiltersMock.length,
@@ -82,7 +151,7 @@ describe('DiscussionFilter component', () => {
});
it('renders the default selected item', () => {
- expect(wrapper.find('#discussion-filter-dropdown .dropdown-item').text().trim()).toBe(
+ expect(wrapper.find('.discussion-filter-container .dropdown-item').text().trim()).toBe(
discussionFiltersMock[0].title,
);
});
@@ -127,14 +196,6 @@ describe('DiscussionFilter component', () => {
expect(wrapper.vm.$store.state.commentsDisabled).toBe(false);
});
- it('renders a dropdown divider for the default filter', () => {
- const defaultFilter = wrapper.findAll(
- `.discussion-filter-container .dropdown-item-wrapper > *`,
- );
-
- expect(defaultFilter.at(1).classes('gl-new-dropdown-divider')).toBe(true);
- });
-
describe('Merge request tabs', () => {
eventHub = createEventHub();
diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js
index 1b8b6bec490..a74d709ed3a 100644
--- a/spec/frontend/notes/components/discussion_notes_spec.js
+++ b/spec/frontend/notes/components/discussion_notes_spec.js
@@ -140,21 +140,21 @@ describe('DiscussionNotes', () => {
findNoteAtIndex(0).vm.$emit('handleDeleteNote');
await nextTick();
- expect(wrapper.emitted().deleteNote).toBeTruthy();
+ expect(wrapper.emitted().deleteNote).toHaveLength(1);
});
it('emits startReplying when first note emits startReplying', async () => {
findNoteAtIndex(0).vm.$emit('startReplying');
await nextTick();
- expect(wrapper.emitted().startReplying).toBeTruthy();
+ expect(wrapper.emitted().startReplying).toHaveLength(1);
});
it('emits deleteNote when second note emits handleDeleteNote', async () => {
findNoteAtIndex(1).vm.$emit('handleDeleteNote');
await nextTick();
- expect(wrapper.emitted().deleteNote).toBeTruthy();
+ expect(wrapper.emitted().deleteNote).toHaveLength(1);
});
});
@@ -169,7 +169,7 @@ describe('DiscussionNotes', () => {
note.vm.$emit('handleDeleteNote');
await nextTick();
- expect(wrapper.emitted().deleteNote).toBeTruthy();
+ expect(wrapper.emitted().deleteNote).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js
index 71406eeb7b4..a185f11ffaa 100644
--- a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js
+++ b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js
@@ -19,7 +19,7 @@ describe('ResolveWithIssueButton', () => {
wrapper.destroy();
});
- it('it should have a link with the provided link property as href', () => {
+ it('should have a link with the provided link property as href', () => {
const button = wrapper.findComponent(GlButton);
expect(button.attributes().href).toBe(url);
diff --git a/spec/frontend/notes/components/multiline_comment_form_spec.js b/spec/frontend/notes/components/multiline_comment_form_spec.js
index b027a261c15..8446bba340f 100644
--- a/spec/frontend/notes/components/multiline_comment_form_spec.js
+++ b/spec/frontend/notes/components/multiline_comment_form_spec.js
@@ -70,7 +70,7 @@ describe('MultilineCommentForm', () => {
glSelect.vm.$emit('change', { ...testLine });
expect(wrapper.vm.commentLineStart).toEqual(line);
- expect(wrapper.emitted('input')).toBeTruthy();
+ expect(wrapper.emitted('input')).toHaveLength(1);
// Once during created, once during updateCommentLineStart
expect(setSelectedCommentPosition).toHaveBeenCalledTimes(2);
});
diff --git a/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js b/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js
new file mode 100644
index 00000000000..658e844a9b1
--- /dev/null
+++ b/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js
@@ -0,0 +1,35 @@
+import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import TimelineEventButton from '~/notes/components/note_actions/timeline_event_button.vue';
+
+const emitData = {
+ noteId: '1',
+ addError: 'Error promoting the note to timeline event: %{error}',
+ addGenericError: 'Something went wrong while promoting the note to timeline event.',
+};
+
+describe('NoteTimelineEventButton', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(TimelineEventButton, {
+ propsData: {
+ noteId: '1',
+ isPromotionInProgress: true,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findTimelineButton = () => wrapper.findComponent(GlButton);
+
+ it('emits click-promote-comment-to-event', async () => {
+ findTimelineButton().vm.$emit('click');
+
+ expect(wrapper.emitted('click-promote-comment-to-event')).toEqual([[emitData]]);
+ expect(findTimelineButton().props('disabled')).toEqual(true);
+ });
+});
diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js
index c2e56d3e7a7..3b5313744ff 100644
--- a/spec/frontend/notes/components/note_body_spec.js
+++ b/spec/frontend/notes/components/note_body_spec.js
@@ -74,11 +74,11 @@ describe('issue_note_body component', () => {
});
it.each`
- confidential | buttonText
- ${false} | ${'Save comment'}
- ${true} | ${'Save internal note'}
- `('renders save button with text "$buttonText"', ({ confidential, buttonText }) => {
- wrapper = createComponent({ props: { note: { ...note, confidential }, isEditing: true } });
+ internal | buttonText
+ ${false} | ${'Save comment'}
+ ${true} | ${'Save internal note'}
+ `('renders save button with text "$buttonText"', ({ internal, buttonText }) => {
+ wrapper = createComponent({ props: { note: { ...note, internal }, isEditing: true } });
expect(wrapper.findComponent(NoteForm).props('saveButtonTitle')).toBe(buttonText);
});
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index fad04e9063d..90473e7ccba 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -116,15 +116,15 @@ describe('issue_note_form component', () => {
});
it.each`
- confidential | placeholder
- ${false} | ${'Write a comment or drag your files here…'}
- ${true} | ${'Write an internal note or drag your files here…'}
+ internal | placeholder
+ ${false} | ${'Write a comment or drag your files here…'}
+ ${true} | ${'Write an internal note or drag your files here…'}
`(
- 'should set correct textarea placeholder text when discussion confidentiality is $confidential',
- ({ confidential, placeholder }) => {
+ 'should set correct textarea placeholder text when discussion confidentiality is $internal',
+ ({ internal, placeholder }) => {
props.note = {
...note,
- confidential,
+ internal,
};
wrapper = createComponentWrapper();
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 43fbc5e26dc..76177229cff 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -3,7 +3,7 @@ import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NoteHeader from '~/notes/components/note_header.vue';
-import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
Vue.use(Vuex);
@@ -40,13 +40,19 @@ describe('NoteHeader component', () => {
availability: '',
};
- const createComponent = (props) => {
+ const createComponent = (props, userAttributes = false) => {
wrapper = shallowMountExtended(NoteHeader, {
store: new Vuex.Store({
actions,
}),
propsData: { ...props },
stubs: { GlSprintf, UserNameWithStatus },
+ provide: {
+ glFeatures: {
+ removeUserAttributesProjects: userAttributes,
+ removeUserAttributesGroups: userAttributes,
+ },
+ },
});
};
@@ -55,6 +61,26 @@ describe('NoteHeader component', () => {
wrapper = null;
});
+ describe('when removeUserAttributesProjects feature flag is enabled', () => {
+ it('does not render busy status', () => {
+ createComponent({ author: { ...author, availability: AVAILABILITY_STATUS.BUSY } }, true);
+
+ expect(wrapper.find('.note-header-info').text()).not.toContain('(Busy)');
+ });
+
+ it('does not render author status', () => {
+ createComponent({ author }, true);
+
+ expect(findAuthorStatus().exists()).toBe(false);
+ });
+
+ it('does not render username', () => {
+ createComponent({ author }, true);
+
+ expect(wrapper.find('.note-header-info').text()).not.toContain('@');
+ });
+ });
+
it('does not render discussion actions when includeToggle is false', () => {
createComponent({
includeToggle: false,
diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js
index b34305688d9..2175849aeb9 100644
--- a/spec/frontend/notes/components/noteable_discussion_spec.js
+++ b/spec/frontend/notes/components/noteable_discussion_spec.js
@@ -97,7 +97,7 @@ describe('noteable_discussion component', () => {
`(
'reply button on form should have title "$saveButtonTitle" when note is $noteType',
async ({ isNoteInternal, saveButtonTitle }) => {
- wrapper.setProps({ discussion: { ...discussionMock, confidential: isNoteInternal } });
+ wrapper.setProps({ discussion: { ...discussionMock, internal: isNoteInternal } });
await nextTick();
const replyPlaceholder = wrapper.findComponent(ReplyPlaceholder);
diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js
index e049c5bc0c8..b044d40cbe4 100644
--- a/spec/frontend/notes/components/noteable_note_spec.js
+++ b/spec/frontend/notes/components/noteable_note_spec.js
@@ -292,7 +292,7 @@ describe('issue_note', () => {
describe('internal note', () => {
it('has internal note class for internal notes', () => {
- createWrapper({ note: { ...note, confidential: true } });
+ createWrapper({ note: { ...note, internal: true } });
expect(wrapper.classes()).toContain('internal-note');
});
diff --git a/spec/frontend/notes/components/sort_discussion_spec.js b/spec/frontend/notes/components/sort_discussion_spec.js
deleted file mode 100644
index 8b6e05da3c0..00000000000
--- a/spec/frontend/notes/components/sort_discussion_spec.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-import Vuex from 'vuex';
-import SortDiscussion from '~/notes/components/sort_discussion.vue';
-import { ASC, DESC } from '~/notes/constants';
-import createStore from '~/notes/stores';
-import Tracking from '~/tracking';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-
-Vue.use(Vuex);
-
-describe('Sort Discussion component', () => {
- let wrapper;
- let store;
-
- const createComponent = () => {
- jest.spyOn(store, 'dispatch').mockImplementation();
-
- wrapper = shallowMount(SortDiscussion, {
- store,
- });
- };
-
- const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
-
- beforeEach(() => {
- store = createStore();
- jest.spyOn(Tracking, 'event');
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('default', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('has local storage sync with the correct props', () => {
- expect(findLocalStorageSync().props('asString')).toBe(true);
- });
-
- it('calls setDiscussionSortDirection when update is emitted', () => {
- findLocalStorageSync().vm.$emit('input', ASC);
-
- expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { direction: ASC });
- });
- });
-
- describe('when asc', () => {
- describe('when the dropdown is clicked', () => {
- it('calls the right actions', () => {
- createComponent();
-
- wrapper.find('.js-newest-first').vm.$emit('click');
-
- expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
- direction: DESC,
- });
- expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
- property: DESC,
- });
- });
- });
-
- it('shows the "Oldest First" as the dropdown', () => {
- createComponent();
-
- expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Oldest first');
- });
- });
-
- describe('when desc', () => {
- beforeEach(() => {
- store.state.discussionSortOrder = DESC;
- createComponent();
- });
-
- describe('when the dropdown item is clicked', () => {
- it('calls the right actions', () => {
- wrapper.find('.js-oldest-first').vm.$emit('click');
-
- expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
- direction: ASC,
- });
- expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
- property: ASC,
- });
- });
-
- it('sets is-checked to true on the active button in the dropdown', () => {
- expect(wrapper.find('.js-newest-first').props('isChecked')).toBe(true);
- });
- });
-
- it('shows the "Newest First" as the dropdown', () => {
- expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Newest first');
- });
- });
-});
diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js
index 35b3dec6298..1b4e8026d84 100644
--- a/spec/frontend/notes/mixins/discussion_navigation_spec.js
+++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js
@@ -110,16 +110,13 @@ describe('Discussion navigation mixin', () => {
});
describe.each`
- fn | args | currentId | expected
- ${'jumpToNextDiscussion'} | ${[]} | ${null} | ${'a'}
- ${'jumpToNextDiscussion'} | ${[]} | ${'a'} | ${'c'}
- ${'jumpToNextDiscussion'} | ${[]} | ${'e'} | ${'a'}
- ${'jumpToPreviousDiscussion'} | ${[]} | ${null} | ${'e'}
- ${'jumpToPreviousDiscussion'} | ${[]} | ${'e'} | ${'c'}
- ${'jumpToPreviousDiscussion'} | ${[]} | ${'c'} | ${'a'}
- ${'jumpToNextRelativeDiscussion'} | ${[null]} | ${null} | ${'a'}
- ${'jumpToNextRelativeDiscussion'} | ${['a']} | ${null} | ${'c'}
- ${'jumpToNextRelativeDiscussion'} | ${['e']} | ${'c'} | ${'a'}
+ fn | args | currentId | expected
+ ${'jumpToNextDiscussion'} | ${[]} | ${null} | ${'a'}
+ ${'jumpToNextDiscussion'} | ${[]} | ${'a'} | ${'c'}
+ ${'jumpToNextDiscussion'} | ${[]} | ${'e'} | ${'a'}
+ ${'jumpToPreviousDiscussion'} | ${[]} | ${null} | ${'e'}
+ ${'jumpToPreviousDiscussion'} | ${[]} | ${'e'} | ${'c'}
+ ${'jumpToPreviousDiscussion'} | ${[]} | ${'c'} | ${'a'}
`('$fn (args = $args, currentId = $currentId)', ({ fn, args, currentId, expected }) => {
beforeEach(() => {
store.state.notes.currentDiscussionId = currentId;
@@ -133,19 +130,12 @@ describe('Discussion navigation mixin', () => {
await nextTick();
});
- it('sets current discussion', () => {
- expect(store.state.notes.currentDiscussionId).toEqual(expected);
- });
-
it('expands discussion', () => {
expect(expandDiscussion).toHaveBeenCalled();
});
it('scrolls to element', () => {
- expect(utils.scrollToElement).toHaveBeenCalledWith(
- findDiscussion('div.discussion', expected),
- { behavior: 'auto' },
- );
+ expect(utils.scrollToElement).toHaveBeenCalled();
});
});
@@ -172,7 +162,7 @@ describe('Discussion navigation mixin', () => {
expect(utils.scrollToElementWithContext).toHaveBeenCalledWith(
findDiscussion('ul.notes', expected),
- { behavior: 'auto' },
+ { behavior: 'auto', offset: 0 },
);
});
});
@@ -213,7 +203,7 @@ describe('Discussion navigation mixin', () => {
it('scrolls to discussion', () => {
expect(utils.scrollToElement).toHaveBeenCalledWith(
findDiscussion('div.discussion', expected),
- { behavior: 'auto' },
+ { behavior: 'auto', offset: 0 },
);
});
});
@@ -244,7 +234,6 @@ describe('Discussion navigation mixin', () => {
it.each`
tabValue
${'diffs'}
- ${'show'}
${'other'}
`(
'calls scrollToFile with setHash as $hashValue when the tab is $tabValue',
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 02b27eca196..989dd74b6d0 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -4,6 +4,7 @@ import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import Api from '~/api';
import createFlash from '~/flash';
+import toast from '~/vue_shared/plugins/global_toast';
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
import axios from '~/lib/utils/axios_utils';
import * as notesConstants from '~/notes/constants';
@@ -14,7 +15,9 @@ import mutations from '~/notes/stores/mutations';
import * as utils from '~/notes/stores/utils';
import updateIssueLockMutation from '~/sidebar/components/lock/mutations/update_issue_lock.mutation.graphql';
import updateMergeRequestLockMutation from '~/sidebar/components/lock/mutations/update_merge_request_lock.mutation.graphql';
+import promoteTimelineEvent from '~/notes/graphql/promote_timeline_event.mutation.graphql';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
+import notesEventHub from '~/notes/event_hub';
import waitForPromises from 'helpers/wait_for_promises';
import { resetStore } from '../helpers';
import {
@@ -38,6 +41,8 @@ jest.mock('~/flash', () => {
return flash;
});
+jest.mock('~/vue_shared/plugins/global_toast');
+
describe('Actions Notes Store', () => {
let commit;
let dispatch;
@@ -1324,6 +1329,102 @@ describe('Actions Notes Store', () => {
});
});
+ describe('promoteCommentToTimelineEvent', () => {
+ const actionArgs = {
+ noteId: '1',
+ addError: 'addError: Create error',
+ addGenericError: 'addGenericError',
+ };
+ const commitSpy = jest.fn();
+
+ describe('for successful request', () => {
+ const timelineEventSuccessResponse = {
+ data: {
+ timelineEventPromoteFromNote: {
+ timelineEvent: {
+ id: 'gid://gitlab/IncidentManagement::TimelineEvent/19',
+ },
+ errors: [],
+ },
+ },
+ };
+
+ beforeEach(() => {
+ jest.spyOn(utils.gqClient, 'mutate').mockResolvedValue(timelineEventSuccessResponse);
+ });
+
+ it('calls gqClient mutation with the correct values', () => {
+ actions.promoteCommentToTimelineEvent({ commit: () => {} }, actionArgs);
+
+ expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1);
+ expect(utils.gqClient.mutate).toHaveBeenCalledWith({
+ mutation: promoteTimelineEvent,
+ variables: {
+ input: {
+ noteId: 'gid://gitlab/Note/1',
+ },
+ },
+ });
+ });
+
+ it('returns success response', () => {
+ jest.spyOn(notesEventHub, '$emit').mockImplementation(() => {});
+
+ return actions.promoteCommentToTimelineEvent({ commit: commitSpy }, actionArgs).then(() => {
+ expect(notesEventHub.$emit).toHaveBeenLastCalledWith(
+ 'comment-promoted-to-timeline-event',
+ );
+ expect(toast).toHaveBeenCalledWith('Comment added to the timeline.');
+ expect(commitSpy).toHaveBeenCalledWith(
+ mutationTypes.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS,
+ false,
+ );
+ });
+ });
+ });
+
+ describe('for failing request', () => {
+ const errorResponse = {
+ data: {
+ timelineEventPromoteFromNote: {
+ timelineEvent: null,
+ errors: ['Create error'],
+ },
+ },
+ };
+
+ it.each`
+ mockReject | message | captureError | error
+ ${true} | ${'addGenericError'} | ${true} | ${new Error()}
+ ${false} | ${'addError: Create error'} | ${false} | ${null}
+ `(
+ 'should show an error when submission fails',
+ ({ mockReject, message, captureError, error }) => {
+ const expectedAlertArgs = {
+ captureError,
+ error,
+ message,
+ };
+ if (mockReject) {
+ jest.spyOn(utils.gqClient, 'mutate').mockRejectedValueOnce(new Error());
+ } else {
+ jest.spyOn(utils.gqClient, 'mutate').mockResolvedValue(errorResponse);
+ }
+
+ return actions
+ .promoteCommentToTimelineEvent({ commit: commitSpy }, actionArgs)
+ .then(() => {
+ expect(createFlash).toHaveBeenCalledWith(expectedAlertArgs);
+ expect(commitSpy).toHaveBeenCalledWith(
+ mutationTypes.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS,
+ false,
+ );
+ });
+ },
+ );
+ });
+ });
+
describe('setFetchingState', () => {
it('commits SET_NOTES_FETCHING_STATE', () => {
return testAction(
diff --git a/spec/frontend/notes/stores/getters_spec.js b/spec/frontend/notes/stores/getters_spec.js
index 6d078dcefcf..e03fa854e54 100644
--- a/spec/frontend/notes/stores/getters_spec.js
+++ b/spec/frontend/notes/stores/getters_spec.js
@@ -211,7 +211,7 @@ describe('Getters Notes Store', () => {
describe('isNotesFetched', () => {
it('should return the state for the fetching notes', () => {
- expect(getters.isNotesFetched(state)).toBeFalsy();
+ expect(getters.isNotesFetched(state)).toBe(false);
});
});
@@ -512,8 +512,8 @@ describe('Getters Notes Store', () => {
unresolvedDiscussionsIdsByDate: [],
};
- expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(true)).toBeFalsy();
- expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeFalsy();
+ expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(true)).toBeUndefined();
+ expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeUndefined();
});
});
diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js
index e0a0fc43ffe..8809a496c52 100644
--- a/spec/frontend/notes/stores/mutation_spec.js
+++ b/spec/frontend/notes/stores/mutation_spec.js
@@ -74,7 +74,7 @@ describe('Notes Store mutations', () => {
});
describe('DELETE_NOTE', () => {
- it('should delete a note ', () => {
+ it('should delete a note', () => {
const state = { discussions: [discussionMock] };
const toDelete = discussionMock.notes[0];
const lengthBefore = discussionMock.notes.length;