diff options
Diffstat (limited to 'spec/frontend/notes')
-rw-r--r-- | spec/frontend/notes/components/diff_with_note_spec.js | 9 | ||||
-rw-r--r-- | spec/frontend/notes/components/discussion_reply_placeholder_spec.js | 2 | ||||
-rw-r--r-- | spec/frontend/notes/components/multiline_comment_utils_spec.js | 49 | ||||
-rw-r--r-- | spec/frontend/notes/components/note_actions_spec.js | 60 | ||||
-rw-r--r-- | spec/frontend/notes/components/note_form_spec.js | 54 | ||||
-rw-r--r-- | spec/frontend/notes/components/noteable_note_spec.js | 53 | ||||
-rw-r--r-- | spec/frontend/notes/mixins/discussion_navigation_spec.js | 12 | ||||
-rw-r--r-- | spec/frontend/notes/mock_data.js | 13 | ||||
-rw-r--r-- | spec/frontend/notes/stores/actions_spec.js | 214 | ||||
-rw-r--r-- | spec/frontend/notes/stores/mutation_spec.js | 117 |
10 files changed, 568 insertions, 15 deletions
diff --git a/spec/frontend/notes/components/diff_with_note_spec.js b/spec/frontend/notes/components/diff_with_note_spec.js index d6d42e1988d..6480af015db 100644 --- a/spec/frontend/notes/components/diff_with_note_spec.js +++ b/spec/frontend/notes/components/diff_with_note_spec.js @@ -1,4 +1,4 @@ -import { mount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import DiffWithNote from '~/notes/components/diff_with_note.vue'; import { createStore } from '~/mr_notes/stores'; @@ -37,7 +37,7 @@ describe('diff_with_note', () => { beforeEach(() => { const diffDiscussion = getJSONFixture(discussionFixture)[0]; - wrapper = mount(DiffWithNote, { + wrapper = shallowMount(DiffWithNote, { propsData: { discussion: diffDiscussion, }, @@ -76,7 +76,10 @@ describe('diff_with_note', () => { describe('image diff', () => { beforeEach(() => { const imageDiscussion = getJSONFixture(imageDiscussionFixture)[0]; - wrapper = mount(DiffWithNote, { propsData: { discussion: imageDiscussion }, store }); + wrapper = shallowMount(DiffWithNote, { + propsData: { discussion: imageDiscussion, diffFile: {} }, + store, + }); }); it('shows image diff', () => { diff --git a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js index a881e44a007..b7b7ec08867 100644 --- a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js +++ b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js @@ -20,7 +20,7 @@ describe('ReplyPlaceholder', () => { wrapper.destroy(); }); - it('emits onClick even on button click', () => { + it('emits onClick event on button click', () => { findButton().trigger('click'); return wrapper.vm.$nextTick().then(() => { diff --git a/spec/frontend/notes/components/multiline_comment_utils_spec.js b/spec/frontend/notes/components/multiline_comment_utils_spec.js new file mode 100644 index 00000000000..261bfb106e7 --- /dev/null +++ b/spec/frontend/notes/components/multiline_comment_utils_spec.js @@ -0,0 +1,49 @@ +import { + getSymbol, + getStartLineNumber, + getEndLineNumber, +} from '~/notes/components/multiline_comment_utils'; + +describe('Multiline comment utilities', () => { + describe('getStartLineNumber', () => { + it.each` + lineCode | type | result + ${'abcdef_1_1'} | ${'old'} | ${'-1'} + ${'abcdef_1_1'} | ${'new'} | ${'+1'} + ${'abcdef_1_1'} | ${null} | ${'1'} + ${'abcdef'} | ${'new'} | ${''} + ${'abcdef'} | ${'old'} | ${''} + ${'abcdef'} | ${null} | ${''} + `('returns line number', ({ lineCode, type, result }) => { + expect(getStartLineNumber({ start_line_code: lineCode, start_line_type: type })).toEqual( + result, + ); + }); + }); + describe('getEndLineNumber', () => { + it.each` + lineCode | type | result + ${'abcdef_1_1'} | ${'old'} | ${'-1'} + ${'abcdef_1_1'} | ${'new'} | ${'+1'} + ${'abcdef_1_1'} | ${null} | ${'1'} + ${'abcdef'} | ${'new'} | ${''} + ${'abcdef'} | ${'old'} | ${''} + ${'abcdef'} | ${null} | ${''} + `('returns line number', ({ lineCode, type, result }) => { + expect(getEndLineNumber({ end_line_code: lineCode, end_line_type: type })).toEqual(result); + }); + }); + describe('getSymbol', () => { + it.each` + type | result + ${'new'} | ${'+'} + ${'old'} | ${'-'} + ${'unused'} | ${''} + ${''} | ${''} + ${null} | ${''} + ${undefined} | ${''} + `('`$type` returns `$result`', ({ type, result }) => { + expect(getSymbol(type)).toEqual(result); + }); + }); +}); diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js index 5d13f587ca7..220ac22d8eb 100644 --- a/spec/frontend/notes/components/note_actions_spec.js +++ b/spec/frontend/notes/components/note_actions_spec.js @@ -4,26 +4,33 @@ import { TEST_HOST } from 'spec/test_constants'; import createStore from '~/notes/stores'; import noteActions from '~/notes/components/note_actions.vue'; import { userDataMock } from '../mock_data'; +import AxiosMockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; describe('noteActions', () => { let wrapper; let store; let props; + let actions; + let axiosMock; - const shallowMountNoteActions = propsData => { + const shallowMountNoteActions = (propsData, computed) => { const localVue = createLocalVue(); return shallowMount(localVue.extend(noteActions), { store, propsData, localVue, + computed, }); }; beforeEach(() => { store = createStore(); + props = { accessLevel: 'Maintainer', - authorId: 26, + authorId: 1, + author: userDataMock, canDelete: true, canEdit: true, canAwardEmoji: true, @@ -33,10 +40,17 @@ describe('noteActions', () => { reportAbusePath: `${TEST_HOST}/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26`, showReply: false, }; + + actions = { + updateAssignees: jest.fn(), + }; + + axiosMock = new AxiosMockAdapter(axios); }); afterEach(() => { wrapper.destroy(); + axiosMock.restore(); }); describe('user is logged in', () => { @@ -76,6 +90,14 @@ describe('noteActions', () => { it('should not show copy link action when `noteUrl` prop is empty', done => { wrapper.setProps({ ...props, + author: { + avatar_url: 'mock_path', + id: 26, + name: 'Example Maintainer', + path: '/ExampleMaintainer', + state: 'active', + username: 'ExampleMaintainer', + }, noteUrl: '', }); @@ -104,6 +126,25 @@ describe('noteActions', () => { }) .catch(done.fail); }); + + it('should be possible to assign or unassign the comment author', () => { + wrapper = shallowMountNoteActions(props, { + targetType: () => 'issue', + }); + + const assignUserButton = wrapper.find('[data-testid="assign-user"]'); + expect(assignUserButton.exists()).toBe(true); + + assignUserButton.trigger('click'); + axiosMock.onPut(`${TEST_HOST}/api/v4/projects/group/project/issues/1`).reply(() => { + expect(actions.updateAssignees).toHaveBeenCalled(); + }); + }); + + it('should not be possible to assign or unassign the comment author in a merge request', () => { + const assignUserButton = wrapper.find('[data-testid="assign-user"]'); + expect(assignUserButton.exists()).toBe(false); + }); }); }); @@ -157,4 +198,19 @@ describe('noteActions', () => { expect(replyButton.exists()).toBe(false); }); }); + + describe('Draft notes', () => { + beforeEach(() => { + store.dispatch('setUserData', userDataMock); + + wrapper = shallowMountNoteActions({ ...props, canResolve: true, isDraft: true }); + }); + + it('should render the right resolve button title', () => { + const resolveButton = wrapper.find({ ref: 'resolveButton' }); + + expect(resolveButton.exists()).toBe(true); + expect(resolveButton.attributes('title')).toBe('Thread stays unresolved'); + }); + }); }); diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js index 8270c148fb5..15802841c57 100644 --- a/spec/frontend/notes/components/note_form_spec.js +++ b/spec/frontend/notes/components/note_form_spec.js @@ -1,8 +1,9 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import createStore from '~/notes/stores'; import NoteForm from '~/notes/components/note_form.vue'; +import batchComments from '~/batch_comments/stores/modules/batch_comments'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; -import { noteableDataMock, notesDataMock } from '../mock_data'; +import { noteableDataMock, notesDataMock, discussionMock } from '../mock_data'; import { getDraft, updateDraft } from '~/lib/utils/autosave'; @@ -245,4 +246,55 @@ describe('issue_note_form component', () => { expect(updateDraft).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent); }); }); + + describe('with batch comments', () => { + beforeEach(() => { + store.registerModule('batchComments', batchComments()); + + wrapper = createComponentWrapper(); + wrapper.setProps({ + ...props, + noteId: '', + discussion: { ...discussionMock, for_commit: false }, + }); + }); + + it('should be possible to cancel', () => { + jest.spyOn(wrapper.vm, 'cancelHandler'); + + return wrapper.vm.$nextTick().then(() => { + const cancelButton = wrapper.find('[data-testid="cancelBatchCommentsEnabled"]'); + cancelButton.trigger('click'); + + expect(wrapper.vm.cancelHandler).toHaveBeenCalledWith(true); + }); + }); + + it('shows resolve checkbox', () => { + expect(wrapper.find('.js-resolve-checkbox').exists()).toBe(true); + }); + + it('hides actions for commits', () => { + wrapper.setProps({ discussion: { for_commit: true } }); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.find('.note-form-actions').text()).not.toContain('Start a review'); + }); + }); + + describe('on enter', () => { + it('should start review or add to review when cmd+enter is pressed', () => { + const textarea = wrapper.find('textarea'); + + jest.spyOn(wrapper.vm, 'handleAddToReview'); + + textarea.setValue('Foo'); + textarea.trigger('keydown.enter', { metaKey: true }); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.vm.handleAddToReview).toHaveBeenCalled(); + }); + }); + }); + }); }); diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js index 0d67b1d87a9..aa3eaa97e20 100644 --- a/spec/frontend/notes/components/noteable_note_spec.js +++ b/spec/frontend/notes/components/noteable_note_spec.js @@ -1,5 +1,5 @@ import { escape } from 'lodash'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { mount, createLocalVue } from '@vue/test-utils'; import createStore from '~/notes/stores'; import issueNote from '~/notes/components/noteable_note.vue'; import NoteHeader from '~/notes/components/note_header.vue'; @@ -8,9 +8,19 @@ import NoteActions from '~/notes/components/note_actions.vue'; import NoteBody from '~/notes/components/note_body.vue'; import { noteableDataMock, notesDataMock, note } from '../mock_data'; +jest.mock('~/vue_shared/mixins/gl_feature_flags_mixin', () => () => ({ + inject: { + glFeatures: { + from: 'glFeatures', + default: () => ({ multilineComments: true }), + }, + }, +})); + describe('issue_note', () => { let store; let wrapper; + const findMultilineComment = () => wrapper.find('[data-testid="multiline-comment"]'); beforeEach(() => { store = createStore(); @@ -18,12 +28,13 @@ describe('issue_note', () => { store.dispatch('setNotesData', notesDataMock); const localVue = createLocalVue(); - wrapper = shallowMount(localVue.extend(issueNote), { + wrapper = mount(localVue.extend(issueNote), { store, propsData: { note, }, localVue, + stubs: ['note-header', 'user-avatar-link', 'note-actions', 'note-body'], }); }); @@ -31,6 +42,44 @@ describe('issue_note', () => { wrapper.destroy(); }); + describe('mutiline comments', () => { + it('should render if has multiline comment', () => { + const position = { + line_range: { + start_line_code: 'abc_1_1', + end_line_code: 'abc_2_2', + }, + }; + wrapper.setProps({ + note: { ...note, position }, + }); + + return wrapper.vm.$nextTick().then(() => { + expect(findMultilineComment().text()).toEqual('Comment on lines 1 to 2'); + }); + }); + + it('should not render if has single line comment', () => { + const position = { + line_range: { + start_line_code: 'abc_1_1', + end_line_code: 'abc_1_1', + }, + }; + wrapper.setProps({ + note: { ...note, position }, + }); + + return wrapper.vm.$nextTick().then(() => { + expect(findMultilineComment().exists()).toBe(false); + }); + }); + + it('should not render if `line_range` is unavailable', () => { + expect(findMultilineComment().exists()).toBe(false); + }); + }); + it('should render user information', () => { const { author } = note; const avatar = wrapper.find(UserAvatarLink); diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js index 120de023099..ae30a36fc81 100644 --- a/spec/frontend/notes/mixins/discussion_navigation_spec.js +++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js @@ -41,7 +41,7 @@ describe('Discussion navigation mixin', () => { .join(''), ); - jest.spyOn(utils, 'scrollToElement'); + jest.spyOn(utils, 'scrollToElementWithContext'); expandDiscussion = jest.fn(); const { actions, ...notesRest } = notesModule(); @@ -102,7 +102,7 @@ describe('Discussion navigation mixin', () => { }); it('scrolls to element', () => { - expect(utils.scrollToElement).toHaveBeenCalledWith( + expect(utils.scrollToElementWithContext).toHaveBeenCalledWith( findDiscussion('div.discussion', expected), ); }); @@ -123,11 +123,13 @@ describe('Discussion navigation mixin', () => { }); it('scrolls when scrollToDiscussion is emitted', () => { - expect(utils.scrollToElement).not.toHaveBeenCalled(); + expect(utils.scrollToElementWithContext).not.toHaveBeenCalled(); eventHub.$emit('scrollToDiscussion'); - expect(utils.scrollToElement).toHaveBeenCalledWith(findDiscussion('ul.notes', expected)); + expect(utils.scrollToElementWithContext).toHaveBeenCalledWith( + findDiscussion('ul.notes', expected), + ); }); }); @@ -167,7 +169,7 @@ describe('Discussion navigation mixin', () => { }); it('scrolls to discussion', () => { - expect(utils.scrollToElement).toHaveBeenCalledWith( + expect(utils.scrollToElementWithContext).toHaveBeenCalledWith( findDiscussion('div.discussion', expected), ); }); diff --git a/spec/frontend/notes/mock_data.js b/spec/frontend/notes/mock_data.js index 980faac2b04..4ff64abe4cc 100644 --- a/spec/frontend/notes/mock_data.js +++ b/spec/frontend/notes/mock_data.js @@ -1254,3 +1254,16 @@ export const discussionFiltersMock = [ value: 2, }, ]; + +export const batchSuggestionsInfoMock = [ + { + suggestionId: 'a123', + noteId: 'b456', + discussionId: 'c789', + }, + { + suggestionId: 'a001', + noteId: 'b002', + discussionId: 'c003', + }, +]; diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js index cbfb9597159..ef87cb3bee7 100644 --- a/spec/frontend/notes/stores/actions_spec.js +++ b/spec/frontend/notes/stores/actions_spec.js @@ -15,6 +15,7 @@ import { userDataMock, noteableDataMock, individualNote, + batchSuggestionsInfoMock, } from '../mock_data'; import axios from '~/lib/utils/axios_utils'; @@ -890,7 +891,23 @@ describe('Actions Notes Store', () => { testSubmitSuggestion(done, () => { expect(commit).not.toHaveBeenCalled(); expect(dispatch).not.toHaveBeenCalled(); - expect(Flash).toHaveBeenCalledWith(`${TEST_ERROR_MESSAGE}.`, 'alert', flashContainer); + 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, + ); }); }); @@ -903,6 +920,130 @@ describe('Actions Notes Store', () => { }); }); + 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; @@ -942,4 +1083,75 @@ describe('Actions Notes Store', () => { ); }); }); + + 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(); + }); + }); + }); + }); + + describe('updateAssignees', () => { + it('update the assignees state', done => { + testAction( + actions.updateAssignees, + [userDataMock.id], + { state: noteableDataMock }, + [{ type: mutationTypes.UPDATE_ASSIGNEES, payload: [userDataMock.id] }], + [], + done, + ); + }); + }); }); diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js index 27e3490d64b..75ef007b78d 100644 --- a/spec/frontend/notes/stores/mutation_spec.js +++ b/spec/frontend/notes/stores/mutation_spec.js @@ -9,6 +9,7 @@ import { noteableDataMock, individualNote, notesWithDescriptionChanges, + batchSuggestionsInfoMock, } from '../mock_data'; const RESOLVED_NOTE = { resolvable: true, resolved: true }; @@ -700,4 +701,120 @@ describe('Notes Store mutations', () => { expect(state.isToggleBlockedIssueWarning).toEqual(false); }); }); + + describe('SET_APPLYING_BATCH_STATE', () => { + const buildDiscussions = suggestionsInfo => { + const suggestions = suggestionsInfo.map(({ suggestionId }) => ({ id: suggestionId })); + + const notes = suggestionsInfo.map(({ noteId }, index) => ({ + id: noteId, + suggestions: [suggestions[index]], + })); + + return suggestionsInfo.map(({ discussionId }, index) => ({ + id: discussionId, + notes: [notes[index]], + })); + }; + + let state; + let batchedSuggestionInfo; + let discussions; + let suggestions; + + beforeEach(() => { + [batchedSuggestionInfo] = batchSuggestionsInfoMock; + suggestions = batchSuggestionsInfoMock.map(({ suggestionId }) => ({ id: suggestionId })); + discussions = buildDiscussions(batchSuggestionsInfoMock); + state = { + batchSuggestionsInfo: [batchedSuggestionInfo], + discussions, + }; + }); + + it('sets is_applying_batch to a boolean value for all batched suggestions', () => { + mutations.SET_APPLYING_BATCH_STATE(state, true); + + const updatedSuggestion = { + ...suggestions[0], + is_applying_batch: true, + }; + + const expectedSuggestions = [updatedSuggestion, suggestions[1]]; + + const actualSuggestions = state.discussions + .map(discussion => discussion.notes.map(n => n.suggestions)) + .flat(2); + + expect(actualSuggestions).toEqual(expectedSuggestions); + }); + }); + + describe('ADD_SUGGESTION_TO_BATCH', () => { + let state; + + beforeEach(() => { + state = { batchSuggestionsInfo: [] }; + }); + + it("adds a suggestion's info to a batch", () => { + const suggestionInfo = { + suggestionId: 'a123', + noteId: 'b456', + discussionId: 'c789', + }; + + mutations.ADD_SUGGESTION_TO_BATCH(state, suggestionInfo); + + expect(state.batchSuggestionsInfo).toEqual([suggestionInfo]); + }); + }); + + describe('REMOVE_SUGGESTION_FROM_BATCH', () => { + let state; + let suggestionInfo1; + let suggestionInfo2; + + beforeEach(() => { + [suggestionInfo1, suggestionInfo2] = batchSuggestionsInfoMock; + + state = { + batchSuggestionsInfo: [suggestionInfo1, suggestionInfo2], + }; + }); + + it("removes a suggestion's info from a batch", () => { + mutations.REMOVE_SUGGESTION_FROM_BATCH(state, suggestionInfo1.suggestionId); + + expect(state.batchSuggestionsInfo).toEqual([suggestionInfo2]); + }); + }); + + describe('CLEAR_SUGGESTION_BATCH', () => { + let state; + + beforeEach(() => { + state = { + batchSuggestionsInfo: batchSuggestionsInfoMock, + }; + }); + + it('removes info for all suggestions from a batch', () => { + mutations.CLEAR_SUGGESTION_BATCH(state); + + expect(state.batchSuggestionsInfo.length).toEqual(0); + }); + }); + + describe('UPDATE_ASSIGNEES', () => { + it('should update assignees', () => { + const state = { + noteableData: noteableDataMock, + }; + + mutations.UPDATE_ASSIGNEES(state, [userDataMock.id]); + + expect(state.noteableData.assignees).toEqual([userDataMock.id]); + }); + }); }); |