debian-mirror-gitlab/spec/frontend/notes/stores/actions_spec.js

1369 lines
38 KiB
JavaScript
Raw Normal View History

2019-07-07 11:18:12 +05:30
import { TEST_HOST } from 'spec/test_constants';
2020-01-01 13:55:28 +05:30
import AxiosMockAdapter from 'axios-mock-adapter';
import Api from '~/api';
2020-10-24 23:57:45 +05:30
import { deprecatedCreateFlash as Flash } from '~/flash';
2020-04-08 14:13:33 +05:30
import * as actions from '~/notes/stores/actions';
2020-11-24 15:15:51 +05:30
import mutations from '~/notes/stores/mutations';
2019-07-07 11:18:12 +05:30
import * as mutationTypes from '~/notes/stores/mutation_types';
import * as notesConstants from '~/notes/constants';
2018-11-08 19:23:39 +05:30
import createStore from '~/notes/stores';
2018-12-05 23:21:45 +05:30
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
2018-03-17 18:26:18 +05:30
import testAction from '../../helpers/vuex_action_helper';
2018-03-27 19:54:05 +05:30
import { resetStore } from '../helpers';
2018-05-09 12:01:36 +05:30
import {
discussionMock,
notesDataMock,
userDataMock,
noteableDataMock,
individualNote,
2020-06-23 00:09:42 +05:30
batchSuggestionsInfoMock,
2018-05-09 12:01:36 +05:30
} from '../mock_data';
2019-09-30 21:07:59 +05:30
import axios from '~/lib/utils/axios_utils';
2020-07-28 23:09:34 +05:30
import * as utils from '~/notes/stores/utils';
2020-10-24 23:57:45 +05:30
import updateIssueConfidentialMutation from '~/sidebar/components/confidential/mutations/update_issue_confidential.mutation.graphql';
import updateMergeRequestLockMutation from '~/sidebar/components/lock/mutations/update_merge_request_lock.mutation.graphql';
import updateIssueLockMutation from '~/sidebar/components/lock/mutations/update_issue_lock.mutation.graphql';
2018-03-17 18:26:18 +05:30
2019-07-31 22:56:46 +05:30
const TEST_ERROR_MESSAGE = 'Test error message';
2020-04-08 14:13:33 +05:30
jest.mock('~/flash');
2019-07-31 22:56:46 +05:30
2018-03-17 18:26:18 +05:30
describe('Actions Notes Store', () => {
2019-07-31 22:56:46 +05:30
let commit;
let dispatch;
let state;
2018-11-08 19:23:39 +05:30
let store;
2019-12-04 20:38:33 +05:30
let axiosMock;
2018-11-08 19:23:39 +05:30
beforeEach(() => {
store = createStore();
2020-04-08 14:13:33 +05:30
commit = jest.fn();
dispatch = jest.fn();
2019-07-31 22:56:46 +05:30
state = {};
2019-12-04 20:38:33 +05:30
axiosMock = new AxiosMockAdapter(axios);
2020-05-24 23:13:21 +05:30
// This is necessary as we query Close issue button at the top of issue page when clicking bottom button
setFixtures(
'<div class="detail-page-header-actions"><button class="btn-close btn-grouped"></button></div>',
);
2018-11-08 19:23:39 +05:30
});
2018-03-27 19:54:05 +05:30
afterEach(() => {
resetStore(store);
2019-12-04 20:38:33 +05:30
axiosMock.restore();
2018-03-27 19:54:05 +05:30
});
2018-03-17 18:26:18 +05:30
describe('setNotesData', () => {
2018-05-09 12:01:36 +05:30
it('should set received notes data', done => {
testAction(
actions.setNotesData,
notesDataMock,
{ notesData: {} },
[{ type: 'SET_NOTES_DATA', payload: notesDataMock }],
[],
done,
);
2018-03-17 18:26:18 +05:30
});
});
describe('setNoteableData', () => {
2018-05-09 12:01:36 +05:30
it('should set received issue data', done => {
testAction(
actions.setNoteableData,
noteableDataMock,
{ noteableData: {} },
[{ type: 'SET_NOTEABLE_DATA', payload: noteableDataMock }],
[],
done,
);
2018-03-17 18:26:18 +05:30
});
});
describe('setUserData', () => {
2018-05-09 12:01:36 +05:30
it('should set received user data', done => {
testAction(
actions.setUserData,
userDataMock,
{ userData: {} },
[{ type: 'SET_USER_DATA', payload: userDataMock }],
[],
done,
);
2018-03-17 18:26:18 +05:30
});
});
describe('setLastFetchedAt', () => {
2018-05-09 12:01:36 +05:30
it('should set received timestamp', done => {
testAction(
actions.setLastFetchedAt,
'timestamp',
{ lastFetchedAt: {} },
[{ type: 'SET_LAST_FETCHED_AT', payload: 'timestamp' }],
[],
done,
);
2018-03-17 18:26:18 +05:30
});
});
describe('setInitialNotes', () => {
2018-05-09 12:01:36 +05:30
it('should set initial notes', done => {
testAction(
actions.setInitialNotes,
[individualNote],
{ notes: [] },
2018-11-08 19:23:39 +05:30
[{ type: 'SET_INITIAL_DISCUSSIONS', payload: [individualNote] }],
2018-05-09 12:01:36 +05:30
[],
done,
);
2018-03-17 18:26:18 +05:30
});
});
describe('setTargetNoteHash', () => {
2018-05-09 12:01:36 +05:30
it('should set target note hash', done => {
testAction(
actions.setTargetNoteHash,
'hash',
{ notes: [] },
[{ type: 'SET_TARGET_NOTE_HASH', payload: 'hash' }],
[],
done,
);
2018-03-17 18:26:18 +05:30
});
});
describe('toggleDiscussion', () => {
2018-05-09 12:01:36 +05:30
it('should toggle discussion', done => {
testAction(
actions.toggleDiscussion,
{ discussionId: discussionMock.id },
{ notes: [discussionMock] },
[{ type: 'TOGGLE_DISCUSSION', payload: { discussionId: discussionMock.id } }],
[],
done,
);
2018-03-17 18:26:18 +05:30
});
});
2018-03-27 19:54:05 +05:30
2018-11-08 19:23:39 +05:30
describe('expandDiscussion', () => {
it('should expand discussion', done => {
testAction(
actions.expandDiscussion,
{ discussionId: discussionMock.id },
{ notes: [discussionMock] },
[{ type: 'EXPAND_DISCUSSION', payload: { discussionId: discussionMock.id } }],
2019-02-15 15:39:39 +05:30
[{ type: 'diffs/renderFileForDiscussionId', payload: discussionMock.id }],
2018-11-08 19:23:39 +05:30
done,
);
});
});
describe('collapseDiscussion', () => {
it('should commit collapse discussion', done => {
testAction(
actions.collapseDiscussion,
{ discussionId: discussionMock.id },
{ notes: [discussionMock] },
[{ type: 'COLLAPSE_DISCUSSION', payload: { discussionId: discussionMock.id } }],
[],
done,
);
});
});
2018-03-27 19:54:05 +05:30
describe('async methods', () => {
beforeEach(() => {
2019-12-04 20:38:33 +05:30
axiosMock.onAny().reply(200, {});
2018-03-27 19:54:05 +05:30
});
describe('closeIssue', () => {
2018-05-09 12:01:36 +05:30
it('sets state as closed', done => {
store
.dispatch('closeIssue', { notesData: { closeIssuePath: '' } })
2018-03-27 19:54:05 +05:30
.then(() => {
expect(store.state.noteableData.state).toEqual('closed');
expect(store.state.isToggleStateButtonLoading).toEqual(false);
done();
})
.catch(done.fail);
});
});
describe('reopenIssue', () => {
2018-05-09 12:01:36 +05:30
it('sets state as reopened', done => {
store
.dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } })
2018-03-27 19:54:05 +05:30
.then(() => {
expect(store.state.noteableData.state).toEqual('reopened');
expect(store.state.isToggleStateButtonLoading).toEqual(false);
done();
})
.catch(done.fail);
});
});
});
describe('emitStateChangedEvent', () => {
it('emits an event on the document', () => {
2018-05-09 12:01:36 +05:30
document.addEventListener('issuable_vue_app:change', event => {
2018-03-27 19:54:05 +05:30
expect(event.detail.data).toEqual({ id: '1', state: 'closed' });
expect(event.detail.isClosed).toEqual(false);
});
store.dispatch('emitStateChangedEvent', { id: '1', state: 'closed' });
});
});
describe('toggleStateButtonLoading', () => {
2018-05-09 12:01:36 +05:30
it('should set loading as true', done => {
testAction(
actions.toggleStateButtonLoading,
true,
{},
[{ type: 'TOGGLE_STATE_BUTTON_LOADING', payload: true }],
[],
done,
);
2018-03-27 19:54:05 +05:30
});
2018-05-09 12:01:36 +05:30
it('should set loading as false', done => {
testAction(
actions.toggleStateButtonLoading,
false,
{},
[{ type: 'TOGGLE_STATE_BUTTON_LOADING', payload: false }],
[],
done,
);
2018-03-27 19:54:05 +05:30
});
});
describe('toggleIssueLocalState', () => {
2018-05-09 12:01:36 +05:30
it('sets issue state as closed', done => {
testAction(actions.toggleIssueLocalState, 'closed', {}, [{ type: 'CLOSE_ISSUE' }], [], done);
2018-03-27 19:54:05 +05:30
});
2018-05-09 12:01:36 +05:30
it('sets issue state as reopened', done => {
2018-11-08 19:23:39 +05:30
testAction(
actions.toggleIssueLocalState,
'reopened',
{},
[{ type: 'REOPEN_ISSUE' }],
[],
done,
);
2018-03-27 19:54:05 +05:30
});
});
2020-05-24 23:13:21 +05:30
describe('toggleBlockedIssueWarning', () => {
it('should set issue warning as true', done => {
testAction(
actions.toggleBlockedIssueWarning,
true,
{},
[{ type: 'TOGGLE_BLOCKED_ISSUE_WARNING', payload: true }],
[],
done,
);
});
2018-03-27 19:54:05 +05:30
2020-05-24 23:13:21 +05:30
it('should set issue warning as false', done => {
testAction(
actions.toggleBlockedIssueWarning,
false,
{},
[{ type: 'TOGGLE_BLOCKED_ISSUE_WARNING', payload: false }],
[],
done,
);
});
});
2020-07-28 23:09:34 +05:30
describe('fetchData', () => {
describe('given there are no notes', () => {
const lastFetchedAt = '13579';
beforeEach(() => {
axiosMock
.onGet(notesDataMock.notesPath)
.replyOnce(200, { notes: [], last_fetched_at: lastFetchedAt });
});
it('should commit SET_LAST_FETCHED_AT', () =>
testAction(
actions.fetchData,
undefined,
{ notesData: notesDataMock },
[{ type: 'SET_LAST_FETCHED_AT', payload: lastFetchedAt }],
[],
));
});
describe('given there are notes', () => {
const lastFetchedAt = '12358';
beforeEach(() => {
axiosMock
.onGet(notesDataMock.notesPath)
.replyOnce(200, { notes: discussionMock.notes, last_fetched_at: lastFetchedAt });
});
it('should dispatch updateOrCreateNotes, startTaskList and commit SET_LAST_FETCHED_AT', () =>
testAction(
actions.fetchData,
undefined,
{ notesData: notesDataMock },
[{ type: 'SET_LAST_FETCHED_AT', payload: lastFetchedAt }],
[
{ type: 'updateOrCreateNotes', payload: discussionMock.notes },
{ type: 'startTaskList' },
],
));
});
});
2020-05-24 23:13:21 +05:30
describe('poll', () => {
2020-04-08 14:13:33 +05:30
beforeEach(done => {
2020-07-28 23:09:34 +05:30
axiosMock
.onGet(notesDataMock.notesPath)
.reply(200, { notes: [], last_fetched_at: '123456' }, { 'poll-interval': '1000' });
2018-03-27 19:54:05 +05:30
2018-05-09 12:01:36 +05:30
store
.dispatch('setNotesData', notesDataMock)
2018-03-27 19:54:05 +05:30
.then(done)
.catch(done.fail);
});
2018-05-09 12:01:36 +05:30
it('calls service with last fetched state', done => {
store
.dispatch('poll')
2020-11-24 15:15:51 +05:30
.then(() => {
jest.advanceTimersByTime(2);
})
2018-03-27 19:54:05 +05:30
.then(() => new Promise(resolve => requestAnimationFrame(resolve)))
.then(() => {
expect(store.state.lastFetchedAt).toBe('123456');
2020-04-08 14:13:33 +05:30
jest.advanceTimersByTime(1500);
2018-03-27 19:54:05 +05:30
})
2018-05-09 12:01:36 +05:30
.then(
() =>
new Promise(resolve => {
requestAnimationFrame(resolve);
}),
)
2018-03-27 19:54:05 +05:30
.then(() => {
2020-07-28 23:09:34 +05:30
const expectedGetRequests = 2;
expect(axiosMock.history.get.length).toBe(expectedGetRequests);
expect(axiosMock.history.get[expectedGetRequests - 1].headers).toMatchObject({
2018-03-27 19:54:05 +05:30
'X-Last-Fetched-At': '123456',
});
})
.then(() => store.dispatch('stopPolling'))
.then(done)
.catch(done.fail);
});
});
2018-11-08 19:23:39 +05:30
describe('setNotesFetchedState', () => {
it('should set notes fetched state', done => {
testAction(
actions.setNotesFetchedState,
true,
{},
[{ type: 'SET_NOTES_FETCHED_STATE', payload: true }],
[],
done,
);
});
});
2018-12-05 23:21:45 +05:30
2019-12-04 20:38:33 +05:30
describe('removeNote', () => {
2019-09-30 21:07:59 +05:30
const endpoint = `${TEST_HOST}/note`;
2018-12-05 23:21:45 +05:30
beforeEach(() => {
2019-09-30 21:07:59 +05:30
axiosMock.onDelete(endpoint).replyOnce(200, {});
2019-02-15 15:39:39 +05:30
2020-04-08 14:13:33 +05:30
document.body.setAttribute('data-page', '');
2018-12-05 23:21:45 +05:30
});
afterEach(() => {
2019-09-30 21:07:59 +05:30
axiosMock.restore();
2019-02-15 15:39:39 +05:30
2020-04-08 14:13:33 +05:30
document.body.setAttribute('data-page', '');
2018-12-05 23:21:45 +05:30
});
it('commits DELETE_NOTE and dispatches updateMergeRequestWidget', done => {
2019-09-30 21:07:59 +05:30
const note = { path: endpoint, id: 1 };
2018-12-05 23:21:45 +05:30
testAction(
2019-12-04 20:38:33 +05:30
actions.removeNote,
2018-12-05 23:21:45 +05:30
note,
store.state,
[
{
type: 'DELETE_NOTE',
payload: note,
},
],
[
{
type: 'updateMergeRequestWidget',
},
2019-02-15 15:39:39 +05:30
{
2019-09-30 21:07:59 +05:30
type: 'updateResolvableDiscussionsCounts',
2019-02-15 15:39:39 +05:30
},
],
done,
);
});
it('dispatches removeDiscussionsFromDiff on merge request page', done => {
2019-09-30 21:07:59 +05:30
const note = { path: endpoint, id: 1 };
2019-02-15 15:39:39 +05:30
2020-04-08 14:13:33 +05:30
document.body.setAttribute('data-page', 'projects:merge_requests:show');
2019-02-15 15:39:39 +05:30
testAction(
2019-12-04 20:38:33 +05:30
actions.removeNote,
2019-02-15 15:39:39 +05:30
note,
store.state,
[
{
type: 'DELETE_NOTE',
payload: note,
},
],
[
{
type: 'updateMergeRequestWidget',
},
{
2019-09-30 21:07:59 +05:30
type: 'updateResolvableDiscussionsCounts',
2019-02-15 15:39:39 +05:30
},
{
type: 'diffs/removeDiscussionsFromDiff',
},
2018-12-05 23:21:45 +05:30
],
done,
);
});
});
2019-12-04 20:38:33 +05:30
describe('deleteNote', () => {
const endpoint = `${TEST_HOST}/note`;
beforeEach(() => {
axiosMock.onDelete(endpoint).replyOnce(200, {});
2020-04-08 14:13:33 +05:30
document.body.setAttribute('data-page', '');
2019-12-04 20:38:33 +05:30
});
afterEach(() => {
axiosMock.restore();
2020-04-08 14:13:33 +05:30
document.body.setAttribute('data-page', '');
2019-12-04 20:38:33 +05:30
});
it('dispatches removeNote', done => {
const note = { path: endpoint, id: 1 };
testAction(
actions.deleteNote,
note,
{},
[],
[
{
type: 'removeNote',
payload: {
id: 1,
path: 'http://test.host/note',
},
},
],
done,
);
});
});
2018-12-05 23:21:45 +05:30
describe('createNewNote', () => {
describe('success', () => {
const res = {
id: 1,
valid: true,
};
beforeEach(() => {
2019-12-04 20:38:33 +05:30
axiosMock.onAny().reply(200, res);
2018-12-05 23:21:45 +05:30
});
it('commits ADD_NEW_NOTE and dispatches updateMergeRequestWidget', done => {
testAction(
actions.createNewNote,
2020-07-28 23:09:34 +05:30
{ endpoint: `${TEST_HOST}`, data: {} },
2018-12-05 23:21:45 +05:30
store.state,
[
{
type: 'ADD_NEW_NOTE',
payload: res,
},
],
[
{
type: 'updateMergeRequestWidget',
},
2019-02-15 15:39:39 +05:30
{
type: 'startTaskList',
},
{
2019-09-30 21:07:59 +05:30
type: 'updateResolvableDiscussionsCounts',
2019-02-15 15:39:39 +05:30
},
2018-12-05 23:21:45 +05:30
],
done,
);
});
});
describe('error', () => {
const res = {
errors: ['error'],
};
beforeEach(() => {
2019-12-04 20:38:33 +05:30
axiosMock.onAny().replyOnce(200, res);
2018-12-05 23:21:45 +05:30
});
it('does not commit ADD_NEW_NOTE or dispatch updateMergeRequestWidget', done => {
testAction(
actions.createNewNote,
2020-07-28 23:09:34 +05:30
{ endpoint: `${TEST_HOST}`, data: {} },
2018-12-05 23:21:45 +05:30
store.state,
[],
[],
done,
);
});
});
});
describe('toggleResolveNote', () => {
const res = {
resolved: true,
};
beforeEach(() => {
2019-12-04 20:38:33 +05:30
axiosMock.onAny().reply(200, res);
2018-12-05 23:21:45 +05:30
});
describe('as note', () => {
it('commits UPDATE_NOTE and dispatches updateMergeRequestWidget', done => {
testAction(
actions.toggleResolveNote,
2020-07-28 23:09:34 +05:30
{ endpoint: `${TEST_HOST}`, isResolved: true, discussion: false },
2018-12-05 23:21:45 +05:30
store.state,
[
{
type: 'UPDATE_NOTE',
payload: res,
},
],
[
2019-02-15 15:39:39 +05:30
{
2019-09-30 21:07:59 +05:30
type: 'updateResolvableDiscussionsCounts',
2019-02-15 15:39:39 +05:30
},
2018-12-05 23:21:45 +05:30
{
type: 'updateMergeRequestWidget',
},
],
done,
);
});
});
describe('as discussion', () => {
it('commits UPDATE_DISCUSSION and dispatches updateMergeRequestWidget', done => {
testAction(
actions.toggleResolveNote,
2020-07-28 23:09:34 +05:30
{ endpoint: `${TEST_HOST}`, isResolved: true, discussion: true },
2018-12-05 23:21:45 +05:30
store.state,
[
{
type: 'UPDATE_DISCUSSION',
payload: res,
},
],
[
2019-02-15 15:39:39 +05:30
{
2019-09-30 21:07:59 +05:30
type: 'updateResolvableDiscussionsCounts',
2019-02-15 15:39:39 +05:30
},
2018-12-05 23:21:45 +05:30
{
type: 'updateMergeRequestWidget',
},
],
done,
);
});
});
});
describe('updateMergeRequestWidget', () => {
it('calls mrWidget checkStatus', () => {
2020-04-08 14:13:33 +05:30
jest.spyOn(mrWidgetEventHub, '$emit').mockImplementation(() => {});
2018-12-05 23:21:45 +05:30
actions.updateMergeRequestWidget();
expect(mrWidgetEventHub.$emit).toHaveBeenCalledWith('mr.discussion.updated');
});
});
2018-12-13 13:39:08 +05:30
describe('setCommentsDisabled', () => {
it('should set comments disabled state', done => {
testAction(
actions.setCommentsDisabled,
true,
null,
[{ type: 'DISABLE_COMMENTS', payload: true }],
[],
done,
);
});
});
2019-02-15 15:39:39 +05:30
2019-09-30 21:07:59 +05:30
describe('updateResolvableDiscussionsCounts', () => {
2019-02-15 15:39:39 +05:30
it('commits UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', done => {
testAction(
2019-09-30 21:07:59 +05:30
actions.updateResolvableDiscussionsCounts,
2019-02-15 15:39:39 +05:30
null,
{},
[{ type: 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS' }],
[],
done,
);
});
});
2019-03-02 22:35:43 +05:30
describe('convertToDiscussion', () => {
it('commits CONVERT_TO_DISCUSSION with noteId', done => {
const noteId = 'dummy-note-id';
testAction(
actions.convertToDiscussion,
noteId,
{},
[{ type: 'CONVERT_TO_DISCUSSION', payload: noteId }],
[],
done,
);
});
});
2019-07-07 11:18:12 +05:30
describe('updateOrCreateNotes', () => {
2020-11-24 15:15:51 +05:30
it('Prevents `fetchDiscussions` being called multiple times within time limit', () => {
jest.useFakeTimers();
const note = { id: 1234, type: notesConstants.DIFF_NOTE };
const getters = { notesById: {} };
state = { discussions: [note], notesData: { discussionsPath: '' } };
commit.mockImplementation((type, value) => {
if (type === mutationTypes.SET_FETCHING_DISCUSSIONS) {
mutations[type](state, value);
}
});
actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]);
actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]);
jest.runAllTimers();
actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]);
expect(dispatch).toHaveBeenCalledTimes(2);
});
2019-07-07 11:18:12 +05:30
it('Updates existing note', () => {
const note = { id: 1234 };
const getters = { notesById: { 1234: note } };
actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]);
2020-04-08 14:13:33 +05:30
expect(commit.mock.calls).toEqual([[mutationTypes.UPDATE_NOTE, note]]);
2019-07-07 11:18:12 +05:30
});
it('Creates a new note if none exisits', () => {
const note = { id: 1234 };
const getters = { notesById: {} };
actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]);
2020-04-08 14:13:33 +05:30
expect(commit.mock.calls).toEqual([[mutationTypes.ADD_NEW_NOTE, note]]);
2019-07-07 11:18:12 +05:30
});
describe('Discussion notes', () => {
let note;
let getters;
beforeEach(() => {
note = { id: 1234 };
getters = { notesById: {} };
});
it('Adds a reply to an existing discussion', () => {
state = { discussions: [note] };
const discussionNote = {
...note,
type: notesConstants.DISCUSSION_NOTE,
discussion_id: 1234,
};
actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [discussionNote]);
2020-04-08 14:13:33 +05:30
expect(commit.mock.calls).toEqual([
2019-07-07 11:18:12 +05:30
[mutationTypes.ADD_NEW_REPLY_TO_DISCUSSION, discussionNote],
]);
});
it('fetches discussions for diff notes', () => {
state = { discussions: [], notesData: { discussionsPath: 'Hello world' } };
const diffNote = { ...note, type: notesConstants.DIFF_NOTE, discussion_id: 1234 };
actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [diffNote]);
2020-04-08 14:13:33 +05:30
expect(dispatch.mock.calls).toEqual([
2019-07-07 11:18:12 +05:30
['fetchDiscussions', { path: state.notesData.discussionsPath }],
]);
});
it('Adds a new note', () => {
state = { discussions: [] };
const discussionNote = {
...note,
type: notesConstants.DISCUSSION_NOTE,
discussion_id: 1234,
};
actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [discussionNote]);
2020-04-08 14:13:33 +05:30
expect(commit.mock.calls).toEqual([[mutationTypes.ADD_NEW_NOTE, discussionNote]]);
2019-07-07 11:18:12 +05:30
});
});
});
describe('replyToDiscussion', () => {
const payload = { endpoint: TEST_HOST, data: {} };
it('updates discussion if response contains disussion', done => {
2019-12-04 20:38:33 +05:30
const discussion = { notes: [] };
axiosMock.onAny().reply(200, { discussion });
2019-07-07 11:18:12 +05:30
testAction(
actions.replyToDiscussion,
payload,
{
notesById: {},
},
2019-12-04 20:38:33 +05:30
[{ type: mutationTypes.UPDATE_DISCUSSION, payload: discussion }],
2019-07-07 11:18:12 +05:30
[
{ type: 'updateMergeRequestWidget' },
{ type: 'startTaskList' },
2019-09-30 21:07:59 +05:30
{ type: 'updateResolvableDiscussionsCounts' },
2019-07-07 11:18:12 +05:30
],
done,
);
});
it('adds a reply to a discussion', done => {
2019-12-04 20:38:33 +05:30
const res = {};
axiosMock.onAny().reply(200, res);
2019-07-07 11:18:12 +05:30
testAction(
actions.replyToDiscussion,
payload,
{
notesById: {},
},
[{ type: mutationTypes.ADD_NEW_REPLY_TO_DISCUSSION, payload: res }],
[],
done,
);
});
});
describe('removeConvertedDiscussion', () => {
it('commits CONVERT_TO_DISCUSSION with noteId', done => {
const noteId = 'dummy-id';
testAction(
actions.removeConvertedDiscussion,
noteId,
{},
[{ type: 'REMOVE_CONVERTED_DISCUSSION', payload: noteId }],
[],
done,
);
});
});
2019-07-31 22:56:46 +05:30
describe('resolveDiscussion', () => {
let getters;
let discussionId;
beforeEach(() => {
discussionId = discussionMock.id;
state.discussions = [discussionMock];
getters = {
isDiscussionResolved: () => false,
};
});
it('when unresolved, dispatches action', done => {
testAction(
actions.resolveDiscussion,
{ discussionId },
{ ...state, ...getters },
[],
[
{
type: 'toggleResolveNote',
payload: {
endpoint: discussionMock.resolve_path,
isResolved: false,
discussion: true,
},
},
],
done,
);
});
it('when resolved, does nothing', done => {
getters.isDiscussionResolved = id => id === discussionId;
testAction(
actions.resolveDiscussion,
{ discussionId },
{ ...state, ...getters },
[],
[],
done,
);
});
});
2019-09-04 21:01:54 +05:30
describe('saveNote', () => {
2020-01-01 13:55:28 +05:30
const flashContainer = {};
const payload = { endpoint: TEST_HOST, data: { 'note[note]': 'some text' }, flashContainer };
2019-09-04 21:01:54 +05:30
describe('if response contains errors', () => {
const res = { errors: { something: ['went wrong'] } };
2020-01-01 13:55:28 +05:30
const error = { message: 'Unprocessable entity', response: { data: res } };
2019-09-04 21:01:54 +05:30
it('throws an error', done => {
actions
.saveNote(
{
commit() {},
2020-01-01 13:55:28 +05:30
dispatch: () => Promise.reject(error),
2019-09-04 21:01:54 +05:30
},
payload,
)
.then(() => done.fail('Expected error to be thrown!'))
2020-01-01 13:55:28 +05:30
.catch(err => {
expect(err).toBe(error);
2020-04-08 14:13:33 +05:30
expect(Flash).not.toHaveBeenCalled();
2020-01-01 13:55:28 +05:30
})
.then(done)
.catch(done.fail);
});
});
describe('if response contains errors.base', () => {
const res = { errors: { base: ['something went wrong'] } };
const error = { message: 'Unprocessable entity', response: { data: res } };
it('sets flash alert using errors.base message', done => {
actions
.saveNote(
{
commit() {},
dispatch: () => Promise.reject(error),
},
{ ...payload, flashContainer },
)
.then(resp => {
expect(resp.hasFlash).toBe(true);
2020-04-08 14:13:33 +05:30
expect(Flash).toHaveBeenCalledWith(
2020-01-01 13:55:28 +05:30
'Your comment could not be submitted because something went wrong',
'alert',
flashContainer,
);
2019-09-04 21:01:54 +05:30
})
2020-01-01 13:55:28 +05:30
.catch(() => done.fail('Expected success response!'))
2019-09-04 21:01:54 +05:30
.then(done)
.catch(done.fail);
});
});
describe('if response contains no errors', () => {
const res = { valid: true };
it('returns the response', done => {
actions
.saveNote(
{
commit() {},
dispatch: () => Promise.resolve(res),
},
payload,
)
.then(data => {
expect(data).toBe(res);
2020-04-08 14:13:33 +05:30
expect(Flash).not.toHaveBeenCalled();
2019-09-04 21:01:54 +05:30
})
.then(done)
.catch(done.fail);
});
});
});
2019-07-31 22:56:46 +05:30
describe('submitSuggestion', () => {
const discussionId = 'discussion-id';
const noteId = 'note-id';
const suggestionId = 'suggestion-id';
let flashContainer;
beforeEach(() => {
2020-04-08 14:13:33 +05:30
jest.spyOn(Api, 'applySuggestion').mockReturnValue(Promise.resolve());
dispatch.mockReturnValue(Promise.resolve());
2019-07-31 22:56:46 +05:30
flashContainer = {};
});
const testSubmitSuggestion = (done, expectFn) => {
actions
.submitSuggestion(
{ commit, dispatch },
{ discussionId, noteId, suggestionId, flashContainer },
)
.then(expectFn)
.then(done)
.catch(done.fail);
};
it('when service success, commits and resolves discussion', done => {
testSubmitSuggestion(done, () => {
2020-04-08 14:13:33 +05:30
expect(commit.mock.calls).toEqual([
2019-07-31 22:56:46 +05:30
[mutationTypes.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }],
]);
2020-04-08 14:13:33 +05:30
expect(dispatch.mock.calls).toEqual([['resolveDiscussion', { discussionId }]]);
expect(Flash).not.toHaveBeenCalled();
2019-07-31 22:56:46 +05:30
});
});
it('when service fails, flashes error message', done => {
const response = { response: { data: { message: TEST_ERROR_MESSAGE } } };
2020-04-08 14:13:33 +05:30
Api.applySuggestion.mockReturnValue(Promise.reject(response));
2019-07-31 22:56:46 +05:30
testSubmitSuggestion(done, () => {
expect(commit).not.toHaveBeenCalled();
expect(dispatch).not.toHaveBeenCalled();
2020-06-23 00:09:42 +05:30
expect(Flash).toHaveBeenCalledWith(TEST_ERROR_MESSAGE, 'alert', flashContainer);
});
});
it('when service fails, and no error message available, uses default message', done => {
const response = { response: 'foo' };
Api.applySuggestion.mockReturnValue(Promise.reject(response));
testSubmitSuggestion(done, () => {
expect(commit).not.toHaveBeenCalled();
expect(dispatch).not.toHaveBeenCalled();
expect(Flash).toHaveBeenCalledWith(
'Something went wrong while applying the suggestion. Please try again.',
'alert',
flashContainer,
);
2019-07-31 22:56:46 +05:30
});
});
it('when resolve discussion fails, fail gracefully', done => {
2020-04-08 14:13:33 +05:30
dispatch.mockReturnValue(Promise.reject());
2019-07-31 22:56:46 +05:30
testSubmitSuggestion(done, () => {
2020-04-08 14:13:33 +05:30
expect(Flash).not.toHaveBeenCalled();
2019-07-31 22:56:46 +05:30
});
});
});
2019-10-12 21:52:04 +05:30
2020-06-23 00:09:42 +05:30
describe('submitSuggestionBatch', () => {
const discussionIds = batchSuggestionsInfoMock.map(({ discussionId }) => discussionId);
const batchSuggestionsInfo = batchSuggestionsInfoMock;
let flashContainer;
beforeEach(() => {
jest.spyOn(Api, 'applySuggestionBatch');
dispatch.mockReturnValue(Promise.resolve());
Api.applySuggestionBatch.mockReturnValue(Promise.resolve());
state = { batchSuggestionsInfo };
flashContainer = {};
});
const testSubmitSuggestionBatch = (done, expectFn) => {
actions
.submitSuggestionBatch({ commit, dispatch, state }, { flashContainer })
.then(expectFn)
.then(done)
.catch(done.fail);
};
it('when service succeeds, commits, resolves discussions, resets batch and applying batch state', done => {
testSubmitSuggestionBatch(done, () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
[mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[0]],
[mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[1]],
[mutationTypes.CLEAR_SUGGESTION_BATCH],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
]);
expect(dispatch.mock.calls).toEqual([
['resolveDiscussion', { discussionId: discussionIds[0] }],
['resolveDiscussion', { discussionId: discussionIds[1] }],
]);
expect(Flash).not.toHaveBeenCalled();
});
});
it('when service fails, flashes error message, resets applying batch state', done => {
const response = { response: { data: { message: TEST_ERROR_MESSAGE } } };
Api.applySuggestionBatch.mockReturnValue(Promise.reject(response));
testSubmitSuggestionBatch(done, () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
]);
expect(dispatch).not.toHaveBeenCalled();
expect(Flash).toHaveBeenCalledWith(TEST_ERROR_MESSAGE, 'alert', flashContainer);
});
});
it('when service fails, and no error message available, uses default message', done => {
const response = { response: 'foo' };
Api.applySuggestionBatch.mockReturnValue(Promise.reject(response));
testSubmitSuggestionBatch(done, () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
]);
expect(dispatch).not.toHaveBeenCalled();
expect(Flash).toHaveBeenCalledWith(
'Something went wrong while applying the batch of suggestions. Please try again.',
'alert',
flashContainer,
);
});
});
it('when resolve discussions fails, fails gracefully, resets batch and applying batch state', done => {
dispatch.mockReturnValue(Promise.reject());
testSubmitSuggestionBatch(done, () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
[mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[0]],
[mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[1]],
[mutationTypes.CLEAR_SUGGESTION_BATCH],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
]);
expect(Flash).not.toHaveBeenCalled();
});
});
});
describe('addSuggestionInfoToBatch', () => {
const suggestionInfo = batchSuggestionsInfoMock[0];
it("adds a suggestion's info to the current batch", done => {
testAction(
actions.addSuggestionInfoToBatch,
suggestionInfo,
{ batchSuggestionsInfo: [] },
[{ type: 'ADD_SUGGESTION_TO_BATCH', payload: suggestionInfo }],
[],
done,
);
});
});
describe('removeSuggestionInfoFromBatch', () => {
const suggestionInfo = batchSuggestionsInfoMock[0];
it("removes a suggestion's info the current batch", done => {
testAction(
actions.removeSuggestionInfoFromBatch,
suggestionInfo.suggestionId,
{ batchSuggestionsInfo: [suggestionInfo] },
[{ type: 'REMOVE_SUGGESTION_FROM_BATCH', payload: suggestionInfo.suggestionId }],
[],
done,
);
});
});
2019-10-12 21:52:04 +05:30
describe('filterDiscussion', () => {
const path = 'some-discussion-path';
const filter = 0;
beforeEach(() => {
2020-04-08 14:13:33 +05:30
dispatch.mockReturnValue(new Promise(() => {}));
2019-10-12 21:52:04 +05:30
});
it('fetches discussions with filter and persistFilter false', () => {
actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: false });
2020-04-08 14:13:33 +05:30
expect(dispatch.mock.calls).toEqual([
2019-10-12 21:52:04 +05:30
['setLoadingState', true],
['fetchDiscussions', { path, filter, persistFilter: false }],
]);
});
it('fetches discussions with filter and persistFilter true', () => {
actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: true });
2020-04-08 14:13:33 +05:30
expect(dispatch.mock.calls).toEqual([
2019-10-12 21:52:04 +05:30
['setLoadingState', true],
['fetchDiscussions', { path, filter, persistFilter: true }],
]);
});
});
2020-04-22 19:07:51 +05:30
describe('setDiscussionSortDirection', () => {
it('calls the correct mutation with the correct args', done => {
testAction(
actions.setDiscussionSortDirection,
2021-01-03 14:25:43 +05:30
{ direction: notesConstants.DESC, persist: false },
2020-04-22 19:07:51 +05:30
{},
2021-01-03 14:25:43 +05:30
[
{
type: mutationTypes.SET_DISCUSSIONS_SORT,
payload: { direction: notesConstants.DESC, persist: false },
},
],
2020-04-22 19:07:51 +05:30
[],
done,
);
});
});
2020-06-23 00:09:42 +05:30
2020-07-28 23:09:34 +05:30
describe('setSelectedCommentPosition', () => {
it('calls the correct mutation with the correct args', done => {
testAction(
actions.setSelectedCommentPosition,
{},
{},
[{ type: mutationTypes.SET_SELECTED_COMMENT_POSITION, payload: {} }],
[],
done,
);
});
});
2020-06-23 00:09:42 +05:30
describe('softDeleteDescriptionVersion', () => {
const endpoint = '/path/to/diff/1';
const payload = {
endpoint,
startingVersion: undefined,
versionId: 1,
};
describe('if response contains no errors', () => {
it('dispatches requestDeleteDescriptionVersion', done => {
axiosMock.onDelete(endpoint).replyOnce(200);
testAction(
actions.softDeleteDescriptionVersion,
payload,
{},
[],
[
{
type: 'requestDeleteDescriptionVersion',
},
{
type: 'receiveDeleteDescriptionVersion',
payload: payload.versionId,
},
],
done,
);
});
});
describe('if response contains errors', () => {
const errorMessage = 'Request failed with status code 503';
it('dispatches receiveDeleteDescriptionVersionError and throws an error', done => {
axiosMock.onDelete(endpoint).replyOnce(503);
testAction(
actions.softDeleteDescriptionVersion,
payload,
{},
[],
[
{
type: 'requestDeleteDescriptionVersion',
},
{
type: 'receiveDeleteDescriptionVersionError',
payload: new Error(errorMessage),
},
],
)
.then(() => done.fail('Expected error to be thrown'))
.catch(() => {
expect(Flash).toHaveBeenCalled();
done();
});
});
});
});
2020-07-28 23:09:34 +05:30
describe('setConfidentiality', () => {
it('calls the correct mutation with the correct args', () => {
testAction(actions.setConfidentiality, true, { noteableData: { confidential: false } }, [
{ type: mutationTypes.SET_ISSUE_CONFIDENTIAL, payload: true },
]);
});
});
2020-06-23 00:09:42 +05:30
describe('updateAssignees', () => {
it('update the assignees state', done => {
testAction(
actions.updateAssignees,
[userDataMock.id],
{ state: noteableDataMock },
[{ type: mutationTypes.UPDATE_ASSIGNEES, payload: [userDataMock.id] }],
[],
done,
);
});
});
2020-07-28 23:09:34 +05:30
2020-10-24 23:57:45 +05:30
describe('updateConfidentialityOnIssuable', () => {
2020-07-28 23:09:34 +05:30
state = { noteableData: { confidential: false } };
const iid = '1';
const projectPath = 'full/path';
const getters = { getNoteableData: { iid } };
const actionArgs = { fullPath: projectPath, confidential: true };
const confidential = true;
beforeEach(() => {
jest
.spyOn(utils.gqClient, 'mutate')
.mockResolvedValue({ data: { issueSetConfidential: { issue: { confidential } } } });
});
it('calls gqClient mutation one time', () => {
2020-10-24 23:57:45 +05:30
actions.updateConfidentialityOnIssuable({ commit: () => {}, state, getters }, actionArgs);
2020-07-28 23:09:34 +05:30
expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1);
});
it('calls gqClient mutation with the correct values', () => {
2020-10-24 23:57:45 +05:30
actions.updateConfidentialityOnIssuable({ commit: () => {}, state, getters }, actionArgs);
2020-07-28 23:09:34 +05:30
expect(utils.gqClient.mutate).toHaveBeenCalledWith({
mutation: updateIssueConfidentialMutation,
variables: { input: { iid, projectPath, confidential } },
});
});
describe('on success of mutation', () => {
it('calls commit with the correct values', () => {
const commitSpy = jest.fn();
return actions
2020-10-24 23:57:45 +05:30
.updateConfidentialityOnIssuable({ commit: commitSpy, state, getters }, actionArgs)
2020-07-28 23:09:34 +05:30
.then(() => {
expect(commitSpy).toHaveBeenCalledWith(
mutationTypes.SET_ISSUE_CONFIDENTIAL,
confidential,
);
});
});
});
});
2020-10-24 23:57:45 +05:30
describe.each`
issuableType
${'issue'} | ${'merge_request'}
`('updateLockedAttribute for issuableType=$issuableType', ({ issuableType }) => {
// Payload for mutation query
state = { noteableData: { discussion_locked: false } };
const targetType = issuableType;
const getters = { getNoteableData: { iid: '1', targetType } };
// Target state after mutation
const locked = true;
const actionArgs = { fullPath: 'full/path', locked };
const input = { iid: '1', projectPath: 'full/path', locked: true };
// Helper functions
const targetMutation = () => {
return targetType === 'issue' ? updateIssueLockMutation : updateMergeRequestLockMutation;
};
const mockResolvedValue = () => {
return targetType === 'issue'
? { data: { issueSetLocked: { issue: { discussionLocked: locked } } } }
: { data: { mergeRequestSetLocked: { mergeRequest: { discussionLocked: locked } } } };
};
beforeEach(() => {
jest.spyOn(utils.gqClient, 'mutate').mockResolvedValue(mockResolvedValue());
});
it('calls gqClient mutation one time', () => {
actions.updateLockedAttribute({ commit: () => {}, state, getters }, actionArgs);
expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1);
});
it('calls gqClient mutation with the correct values', () => {
actions.updateLockedAttribute({ commit: () => {}, state, getters }, actionArgs);
expect(utils.gqClient.mutate).toHaveBeenCalledWith({
mutation: targetMutation(),
variables: { input },
});
});
describe('on success of mutation', () => {
it('calls commit with the correct values', () => {
const commitSpy = jest.fn();
return actions
.updateLockedAttribute({ commit: commitSpy, state, getters }, actionArgs)
.then(() => {
expect(commitSpy).toHaveBeenCalledWith(mutationTypes.SET_ISSUABLE_LOCK, locked);
});
});
});
});
describe('updateDiscussionPosition', () => {
it('update the assignees state', done => {
const updatedPosition = { discussionId: 1, position: { test: true } };
testAction(
actions.updateDiscussionPosition,
updatedPosition,
{ state: { discussions: [] } },
[{ type: mutationTypes.UPDATE_DISCUSSION_POSITION, payload: updatedPosition }],
[],
done,
);
});
});
2018-03-17 18:26:18 +05:30
});