1087 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			1087 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| import { TEST_HOST } from 'spec/test_constants';
 | |
| import AxiosMockAdapter from 'axios-mock-adapter';
 | |
| import Api from '~/api';
 | |
| import Flash from '~/flash';
 | |
| import * as actions from '~/notes/stores/actions';
 | |
| import * as mutationTypes from '~/notes/stores/mutation_types';
 | |
| import * as notesConstants from '~/notes/constants';
 | |
| import createStore from '~/notes/stores';
 | |
| import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
 | |
| import testAction from '../../helpers/vuex_action_helper';
 | |
| import { resetStore } from '../helpers';
 | |
| import {
 | |
|   discussionMock,
 | |
|   notesDataMock,
 | |
|   userDataMock,
 | |
|   noteableDataMock,
 | |
|   individualNote,
 | |
|   batchSuggestionsInfoMock,
 | |
| } from '../mock_data';
 | |
| import axios from '~/lib/utils/axios_utils';
 | |
| 
 | |
| const TEST_ERROR_MESSAGE = 'Test error message';
 | |
| jest.mock('~/flash');
 | |
| 
 | |
| describe('Actions Notes Store', () => {
 | |
|   let commit;
 | |
|   let dispatch;
 | |
|   let state;
 | |
|   let store;
 | |
|   let axiosMock;
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     store = createStore();
 | |
|     commit = jest.fn();
 | |
|     dispatch = jest.fn();
 | |
|     state = {};
 | |
|     axiosMock = new AxiosMockAdapter(axios);
 | |
| 
 | |
|     // 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>',
 | |
|     );
 | |
|   });
 | |
| 
 | |
|   afterEach(() => {
 | |
|     resetStore(store);
 | |
|     axiosMock.restore();
 | |
|   });
 | |
| 
 | |
|   describe('setNotesData', () => {
 | |
|     it('should set received notes data', done => {
 | |
|       testAction(
 | |
|         actions.setNotesData,
 | |
|         notesDataMock,
 | |
|         { notesData: {} },
 | |
|         [{ type: 'SET_NOTES_DATA', payload: notesDataMock }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('setNoteableData', () => {
 | |
|     it('should set received issue data', done => {
 | |
|       testAction(
 | |
|         actions.setNoteableData,
 | |
|         noteableDataMock,
 | |
|         { noteableData: {} },
 | |
|         [{ type: 'SET_NOTEABLE_DATA', payload: noteableDataMock }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('setUserData', () => {
 | |
|     it('should set received user data', done => {
 | |
|       testAction(
 | |
|         actions.setUserData,
 | |
|         userDataMock,
 | |
|         { userData: {} },
 | |
|         [{ type: 'SET_USER_DATA', payload: userDataMock }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('setLastFetchedAt', () => {
 | |
|     it('should set received timestamp', done => {
 | |
|       testAction(
 | |
|         actions.setLastFetchedAt,
 | |
|         'timestamp',
 | |
|         { lastFetchedAt: {} },
 | |
|         [{ type: 'SET_LAST_FETCHED_AT', payload: 'timestamp' }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('setInitialNotes', () => {
 | |
|     it('should set initial notes', done => {
 | |
|       testAction(
 | |
|         actions.setInitialNotes,
 | |
|         [individualNote],
 | |
|         { notes: [] },
 | |
|         [{ type: 'SET_INITIAL_DISCUSSIONS', payload: [individualNote] }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('setTargetNoteHash', () => {
 | |
|     it('should set target note hash', done => {
 | |
|       testAction(
 | |
|         actions.setTargetNoteHash,
 | |
|         'hash',
 | |
|         { notes: [] },
 | |
|         [{ type: 'SET_TARGET_NOTE_HASH', payload: 'hash' }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('toggleDiscussion', () => {
 | |
|     it('should toggle discussion', done => {
 | |
|       testAction(
 | |
|         actions.toggleDiscussion,
 | |
|         { discussionId: discussionMock.id },
 | |
|         { notes: [discussionMock] },
 | |
|         [{ type: 'TOGGLE_DISCUSSION', payload: { discussionId: discussionMock.id } }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('expandDiscussion', () => {
 | |
|     it('should expand discussion', done => {
 | |
|       testAction(
 | |
|         actions.expandDiscussion,
 | |
|         { discussionId: discussionMock.id },
 | |
|         { notes: [discussionMock] },
 | |
|         [{ type: 'EXPAND_DISCUSSION', payload: { discussionId: discussionMock.id } }],
 | |
|         [{ type: 'diffs/renderFileForDiscussionId', payload: discussionMock.id }],
 | |
|         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,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('async methods', () => {
 | |
|     beforeEach(() => {
 | |
|       axiosMock.onAny().reply(200, {});
 | |
|     });
 | |
| 
 | |
|     describe('closeIssue', () => {
 | |
|       it('sets state as closed', done => {
 | |
|         store
 | |
|           .dispatch('closeIssue', { notesData: { closeIssuePath: '' } })
 | |
|           .then(() => {
 | |
|             expect(store.state.noteableData.state).toEqual('closed');
 | |
|             expect(store.state.isToggleStateButtonLoading).toEqual(false);
 | |
|             done();
 | |
|           })
 | |
|           .catch(done.fail);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('reopenIssue', () => {
 | |
|       it('sets state as reopened', done => {
 | |
|         store
 | |
|           .dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } })
 | |
|           .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', () => {
 | |
|       document.addEventListener('issuable_vue_app:change', event => {
 | |
|         expect(event.detail.data).toEqual({ id: '1', state: 'closed' });
 | |
|         expect(event.detail.isClosed).toEqual(false);
 | |
|       });
 | |
| 
 | |
|       store.dispatch('emitStateChangedEvent', { id: '1', state: 'closed' });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('toggleStateButtonLoading', () => {
 | |
|     it('should set loading as true', done => {
 | |
|       testAction(
 | |
|         actions.toggleStateButtonLoading,
 | |
|         true,
 | |
|         {},
 | |
|         [{ type: 'TOGGLE_STATE_BUTTON_LOADING', payload: true }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should set loading as false', done => {
 | |
|       testAction(
 | |
|         actions.toggleStateButtonLoading,
 | |
|         false,
 | |
|         {},
 | |
|         [{ type: 'TOGGLE_STATE_BUTTON_LOADING', payload: false }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('toggleIssueLocalState', () => {
 | |
|     it('sets issue state as closed', done => {
 | |
|       testAction(actions.toggleIssueLocalState, 'closed', {}, [{ type: 'CLOSE_ISSUE' }], [], done);
 | |
|     });
 | |
| 
 | |
|     it('sets issue state as reopened', done => {
 | |
|       testAction(
 | |
|         actions.toggleIssueLocalState,
 | |
|         'reopened',
 | |
|         {},
 | |
|         [{ type: 'REOPEN_ISSUE' }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('toggleBlockedIssueWarning', () => {
 | |
|     it('should set issue warning as true', done => {
 | |
|       testAction(
 | |
|         actions.toggleBlockedIssueWarning,
 | |
|         true,
 | |
|         {},
 | |
|         [{ type: 'TOGGLE_BLOCKED_ISSUE_WARNING', payload: true }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('should set issue warning as false', done => {
 | |
|       testAction(
 | |
|         actions.toggleBlockedIssueWarning,
 | |
|         false,
 | |
|         {},
 | |
|         [{ type: 'TOGGLE_BLOCKED_ISSUE_WARNING', payload: false }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('poll', () => {
 | |
|     beforeEach(done => {
 | |
|       jest.spyOn(axios, 'get');
 | |
| 
 | |
|       store
 | |
|         .dispatch('setNotesData', notesDataMock)
 | |
|         .then(done)
 | |
|         .catch(done.fail);
 | |
|     });
 | |
| 
 | |
|     it('calls service with last fetched state', done => {
 | |
|       axiosMock
 | |
|         .onAny()
 | |
|         .reply(200, { notes: [], last_fetched_at: '123456' }, { 'poll-interval': '1000' });
 | |
| 
 | |
|       store
 | |
|         .dispatch('poll')
 | |
|         .then(() => new Promise(resolve => requestAnimationFrame(resolve)))
 | |
|         .then(() => {
 | |
|           expect(axios.get).toHaveBeenCalled();
 | |
|           expect(store.state.lastFetchedAt).toBe('123456');
 | |
| 
 | |
|           jest.advanceTimersByTime(1500);
 | |
|         })
 | |
|         .then(
 | |
|           () =>
 | |
|             new Promise(resolve => {
 | |
|               requestAnimationFrame(resolve);
 | |
|             }),
 | |
|         )
 | |
|         .then(() => {
 | |
|           expect(axios.get.mock.calls.length).toBe(2);
 | |
|           expect(axios.get.mock.calls[axios.get.mock.calls.length - 1][1].headers).toEqual({
 | |
|             'X-Last-Fetched-At': '123456',
 | |
|           });
 | |
|         })
 | |
|         .then(() => store.dispatch('stopPolling'))
 | |
|         .then(done)
 | |
|         .catch(done.fail);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('setNotesFetchedState', () => {
 | |
|     it('should set notes fetched state', done => {
 | |
|       testAction(
 | |
|         actions.setNotesFetchedState,
 | |
|         true,
 | |
|         {},
 | |
|         [{ type: 'SET_NOTES_FETCHED_STATE', payload: true }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('removeNote', () => {
 | |
|     const endpoint = `${TEST_HOST}/note`;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       axiosMock.onDelete(endpoint).replyOnce(200, {});
 | |
| 
 | |
|       document.body.setAttribute('data-page', '');
 | |
|     });
 | |
| 
 | |
|     afterEach(() => {
 | |
|       axiosMock.restore();
 | |
| 
 | |
|       document.body.setAttribute('data-page', '');
 | |
|     });
 | |
| 
 | |
|     it('commits DELETE_NOTE and dispatches updateMergeRequestWidget', done => {
 | |
|       const note = { path: endpoint, id: 1 };
 | |
| 
 | |
|       testAction(
 | |
|         actions.removeNote,
 | |
|         note,
 | |
|         store.state,
 | |
|         [
 | |
|           {
 | |
|             type: 'DELETE_NOTE',
 | |
|             payload: note,
 | |
|           },
 | |
|         ],
 | |
|         [
 | |
|           {
 | |
|             type: 'updateMergeRequestWidget',
 | |
|           },
 | |
|           {
 | |
|             type: 'updateResolvableDiscussionsCounts',
 | |
|           },
 | |
|         ],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('dispatches removeDiscussionsFromDiff on merge request page', done => {
 | |
|       const note = { path: endpoint, id: 1 };
 | |
| 
 | |
|       document.body.setAttribute('data-page', 'projects:merge_requests:show');
 | |
| 
 | |
|       testAction(
 | |
|         actions.removeNote,
 | |
|         note,
 | |
|         store.state,
 | |
|         [
 | |
|           {
 | |
|             type: 'DELETE_NOTE',
 | |
|             payload: note,
 | |
|           },
 | |
|         ],
 | |
|         [
 | |
|           {
 | |
|             type: 'updateMergeRequestWidget',
 | |
|           },
 | |
|           {
 | |
|             type: 'updateResolvableDiscussionsCounts',
 | |
|           },
 | |
|           {
 | |
|             type: 'diffs/removeDiscussionsFromDiff',
 | |
|           },
 | |
|         ],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('deleteNote', () => {
 | |
|     const endpoint = `${TEST_HOST}/note`;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       axiosMock.onDelete(endpoint).replyOnce(200, {});
 | |
| 
 | |
|       document.body.setAttribute('data-page', '');
 | |
|     });
 | |
| 
 | |
|     afterEach(() => {
 | |
|       axiosMock.restore();
 | |
| 
 | |
|       document.body.setAttribute('data-page', '');
 | |
|     });
 | |
| 
 | |
|     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,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('createNewNote', () => {
 | |
|     describe('success', () => {
 | |
|       const res = {
 | |
|         id: 1,
 | |
|         valid: true,
 | |
|       };
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         axiosMock.onAny().reply(200, res);
 | |
|       });
 | |
| 
 | |
|       it('commits ADD_NEW_NOTE and dispatches updateMergeRequestWidget', done => {
 | |
|         testAction(
 | |
|           actions.createNewNote,
 | |
|           { endpoint: `${gl.TEST_HOST}`, data: {} },
 | |
|           store.state,
 | |
|           [
 | |
|             {
 | |
|               type: 'ADD_NEW_NOTE',
 | |
|               payload: res,
 | |
|             },
 | |
|           ],
 | |
|           [
 | |
|             {
 | |
|               type: 'updateMergeRequestWidget',
 | |
|             },
 | |
|             {
 | |
|               type: 'startTaskList',
 | |
|             },
 | |
|             {
 | |
|               type: 'updateResolvableDiscussionsCounts',
 | |
|             },
 | |
|           ],
 | |
|           done,
 | |
|         );
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('error', () => {
 | |
|       const res = {
 | |
|         errors: ['error'],
 | |
|       };
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         axiosMock.onAny().replyOnce(200, res);
 | |
|       });
 | |
| 
 | |
|       it('does not commit ADD_NEW_NOTE or dispatch updateMergeRequestWidget', done => {
 | |
|         testAction(
 | |
|           actions.createNewNote,
 | |
|           { endpoint: `${gl.TEST_HOST}`, data: {} },
 | |
|           store.state,
 | |
|           [],
 | |
|           [],
 | |
|           done,
 | |
|         );
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('toggleResolveNote', () => {
 | |
|     const res = {
 | |
|       resolved: true,
 | |
|     };
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       axiosMock.onAny().reply(200, res);
 | |
|     });
 | |
| 
 | |
|     describe('as note', () => {
 | |
|       it('commits UPDATE_NOTE and dispatches updateMergeRequestWidget', done => {
 | |
|         testAction(
 | |
|           actions.toggleResolveNote,
 | |
|           { endpoint: `${gl.TEST_HOST}`, isResolved: true, discussion: false },
 | |
|           store.state,
 | |
|           [
 | |
|             {
 | |
|               type: 'UPDATE_NOTE',
 | |
|               payload: res,
 | |
|             },
 | |
|           ],
 | |
|           [
 | |
|             {
 | |
|               type: 'updateResolvableDiscussionsCounts',
 | |
|             },
 | |
|             {
 | |
|               type: 'updateMergeRequestWidget',
 | |
|             },
 | |
|           ],
 | |
|           done,
 | |
|         );
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('as discussion', () => {
 | |
|       it('commits UPDATE_DISCUSSION and dispatches updateMergeRequestWidget', done => {
 | |
|         testAction(
 | |
|           actions.toggleResolveNote,
 | |
|           { endpoint: `${gl.TEST_HOST}`, isResolved: true, discussion: true },
 | |
|           store.state,
 | |
|           [
 | |
|             {
 | |
|               type: 'UPDATE_DISCUSSION',
 | |
|               payload: res,
 | |
|             },
 | |
|           ],
 | |
|           [
 | |
|             {
 | |
|               type: 'updateResolvableDiscussionsCounts',
 | |
|             },
 | |
|             {
 | |
|               type: 'updateMergeRequestWidget',
 | |
|             },
 | |
|           ],
 | |
|           done,
 | |
|         );
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('updateMergeRequestWidget', () => {
 | |
|     it('calls mrWidget checkStatus', () => {
 | |
|       jest.spyOn(mrWidgetEventHub, '$emit').mockImplementation(() => {});
 | |
| 
 | |
|       actions.updateMergeRequestWidget();
 | |
| 
 | |
|       expect(mrWidgetEventHub.$emit).toHaveBeenCalledWith('mr.discussion.updated');
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('setCommentsDisabled', () => {
 | |
|     it('should set comments disabled state', done => {
 | |
|       testAction(
 | |
|         actions.setCommentsDisabled,
 | |
|         true,
 | |
|         null,
 | |
|         [{ type: 'DISABLE_COMMENTS', payload: true }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('updateResolvableDiscussionsCounts', () => {
 | |
|     it('commits UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', done => {
 | |
|       testAction(
 | |
|         actions.updateResolvableDiscussionsCounts,
 | |
|         null,
 | |
|         {},
 | |
|         [{ type: 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS' }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   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,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('updateOrCreateNotes', () => {
 | |
|     it('Updates existing note', () => {
 | |
|       const note = { id: 1234 };
 | |
|       const getters = { notesById: { 1234: note } };
 | |
| 
 | |
|       actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]);
 | |
| 
 | |
|       expect(commit.mock.calls).toEqual([[mutationTypes.UPDATE_NOTE, note]]);
 | |
|     });
 | |
| 
 | |
|     it('Creates a new note if none exisits', () => {
 | |
|       const note = { id: 1234 };
 | |
|       const getters = { notesById: {} };
 | |
|       actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]);
 | |
| 
 | |
|       expect(commit.mock.calls).toEqual([[mutationTypes.ADD_NEW_NOTE, note]]);
 | |
|     });
 | |
| 
 | |
|     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]);
 | |
| 
 | |
|         expect(commit.mock.calls).toEqual([
 | |
|           [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]);
 | |
| 
 | |
|         expect(dispatch.mock.calls).toEqual([
 | |
|           ['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]);
 | |
| 
 | |
|         expect(commit.mock.calls).toEqual([[mutationTypes.ADD_NEW_NOTE, discussionNote]]);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('replyToDiscussion', () => {
 | |
|     const payload = { endpoint: TEST_HOST, data: {} };
 | |
| 
 | |
|     it('updates discussion if response contains disussion', done => {
 | |
|       const discussion = { notes: [] };
 | |
|       axiosMock.onAny().reply(200, { discussion });
 | |
| 
 | |
|       testAction(
 | |
|         actions.replyToDiscussion,
 | |
|         payload,
 | |
|         {
 | |
|           notesById: {},
 | |
|         },
 | |
|         [{ type: mutationTypes.UPDATE_DISCUSSION, payload: discussion }],
 | |
|         [
 | |
|           { type: 'updateMergeRequestWidget' },
 | |
|           { type: 'startTaskList' },
 | |
|           { type: 'updateResolvableDiscussionsCounts' },
 | |
|         ],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('adds a reply to a discussion', done => {
 | |
|       const res = {};
 | |
|       axiosMock.onAny().reply(200, res);
 | |
| 
 | |
|       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,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   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,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('saveNote', () => {
 | |
|     const flashContainer = {};
 | |
|     const payload = { endpoint: TEST_HOST, data: { 'note[note]': 'some text' }, flashContainer };
 | |
| 
 | |
|     describe('if response contains errors', () => {
 | |
|       const res = { errors: { something: ['went wrong'] } };
 | |
|       const error = { message: 'Unprocessable entity', response: { data: res } };
 | |
| 
 | |
|       it('throws an error', done => {
 | |
|         actions
 | |
|           .saveNote(
 | |
|             {
 | |
|               commit() {},
 | |
|               dispatch: () => Promise.reject(error),
 | |
|             },
 | |
|             payload,
 | |
|           )
 | |
|           .then(() => done.fail('Expected error to be thrown!'))
 | |
|           .catch(err => {
 | |
|             expect(err).toBe(error);
 | |
|             expect(Flash).not.toHaveBeenCalled();
 | |
|           })
 | |
|           .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);
 | |
|             expect(Flash).toHaveBeenCalledWith(
 | |
|               'Your comment could not be submitted because something went wrong',
 | |
|               'alert',
 | |
|               flashContainer,
 | |
|             );
 | |
|           })
 | |
|           .catch(() => done.fail('Expected success response!'))
 | |
|           .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);
 | |
|             expect(Flash).not.toHaveBeenCalled();
 | |
|           })
 | |
|           .then(done)
 | |
|           .catch(done.fail);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('submitSuggestion', () => {
 | |
|     const discussionId = 'discussion-id';
 | |
|     const noteId = 'note-id';
 | |
|     const suggestionId = 'suggestion-id';
 | |
|     let flashContainer;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       jest.spyOn(Api, 'applySuggestion').mockReturnValue(Promise.resolve());
 | |
|       dispatch.mockReturnValue(Promise.resolve());
 | |
|       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, () => {
 | |
|         expect(commit.mock.calls).toEqual([
 | |
|           [mutationTypes.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }],
 | |
|         ]);
 | |
| 
 | |
|         expect(dispatch.mock.calls).toEqual([['resolveDiscussion', { discussionId }]]);
 | |
|         expect(Flash).not.toHaveBeenCalled();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('when service fails, flashes error message', done => {
 | |
|       const response = { response: { data: { message: TEST_ERROR_MESSAGE } } };
 | |
| 
 | |
|       Api.applySuggestion.mockReturnValue(Promise.reject(response));
 | |
| 
 | |
|       testSubmitSuggestion(done, () => {
 | |
|         expect(commit).not.toHaveBeenCalled();
 | |
|         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.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,
 | |
|         );
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('when resolve discussion fails, fail gracefully', done => {
 | |
|       dispatch.mockReturnValue(Promise.reject());
 | |
| 
 | |
|       testSubmitSuggestion(done, () => {
 | |
|         expect(Flash).not.toHaveBeenCalled();
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   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,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('filterDiscussion', () => {
 | |
|     const path = 'some-discussion-path';
 | |
|     const filter = 0;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       dispatch.mockReturnValue(new Promise(() => {}));
 | |
|     });
 | |
| 
 | |
|     it('fetches discussions with filter and persistFilter false', () => {
 | |
|       actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: false });
 | |
| 
 | |
|       expect(dispatch.mock.calls).toEqual([
 | |
|         ['setLoadingState', true],
 | |
|         ['fetchDiscussions', { path, filter, persistFilter: false }],
 | |
|       ]);
 | |
|     });
 | |
| 
 | |
|     it('fetches discussions with filter and persistFilter true', () => {
 | |
|       actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: true });
 | |
| 
 | |
|       expect(dispatch.mock.calls).toEqual([
 | |
|         ['setLoadingState', true],
 | |
|         ['fetchDiscussions', { path, filter, persistFilter: true }],
 | |
|       ]);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('setDiscussionSortDirection', () => {
 | |
|     it('calls the correct mutation with the correct args', done => {
 | |
|       testAction(
 | |
|         actions.setDiscussionSortDirection,
 | |
|         notesConstants.DESC,
 | |
|         {},
 | |
|         [{ type: mutationTypes.SET_DISCUSSIONS_SORT, payload: notesConstants.DESC }],
 | |
|         [],
 | |
|         done,
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| });
 |