diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 15:26:25 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 15:26:25 +0300 |
commit | a09983ae35713f5a2bbb100981116d31ce99826e (patch) | |
tree | 2ee2af7bd104d57086db360a7e6d8c9d5d43667a /spec/frontend/notes | |
parent | 18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff) |
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'spec/frontend/notes')
-rw-r--r-- | spec/frontend/notes/components/multiline_comment_utils_spec.js | 64 | ||||
-rw-r--r-- | spec/frontend/notes/components/note_actions_spec.js | 64 | ||||
-rw-r--r-- | spec/frontend/notes/components/note_form_spec.js | 18 | ||||
-rw-r--r-- | spec/frontend/notes/components/noteable_note_spec.js | 71 | ||||
-rw-r--r-- | spec/frontend/notes/mixins/discussion_navigation_spec.js | 6 | ||||
-rw-r--r-- | spec/frontend/notes/old_notes_spec.js | 44 | ||||
-rw-r--r-- | spec/frontend/notes/stores/actions_spec.js | 133 | ||||
-rw-r--r-- | spec/frontend/notes/stores/mutation_spec.js | 34 |
8 files changed, 375 insertions, 59 deletions
diff --git a/spec/frontend/notes/components/multiline_comment_utils_spec.js b/spec/frontend/notes/components/multiline_comment_utils_spec.js index 261bfb106e7..af4394cc648 100644 --- a/spec/frontend/notes/components/multiline_comment_utils_spec.js +++ b/spec/frontend/notes/components/multiline_comment_utils_spec.js @@ -2,35 +2,23 @@ import { getSymbol, getStartLineNumber, getEndLineNumber, + getCommentedLines, } from '~/notes/components/multiline_comment_utils'; describe('Multiline comment utilities', () => { - describe('getStartLineNumber', () => { + describe('get start & end line numbers', () => { + const lineRanges = ['old', 'new', null].map(type => ({ + start: { new_line: 1, old_line: 1, type }, + end: { new_line: 2, old_line: 2, type }, + })); 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); + lineRange | start | end + ${lineRanges[0]} | ${'-1'} | ${'-2'} + ${lineRanges[1]} | ${'+1'} | ${'+2'} + ${lineRanges[2]} | ${'1'} | ${'2'} + `('returns line numbers `$start` & `$end`', ({ lineRange, start, end }) => { + expect(getStartLineNumber(lineRange)).toEqual(start); + expect(getEndLineNumber(lineRange)).toEqual(end); }); }); describe('getSymbol', () => { @@ -46,4 +34,30 @@ describe('Multiline comment utilities', () => { expect(getSymbol(type)).toEqual(result); }); }); + describe('getCommentedLines', () => { + const diffLines = [{ line_code: '1' }, { line_code: '2' }, { line_code: '3' }]; + it('returns a default object when `selectedCommentPosition` is not provided', () => { + expect(getCommentedLines(undefined, diffLines)).toEqual({ startLine: 4, endLine: 4 }); + }); + it('returns an object with startLine and endLine equal to 0', () => { + const selectedCommentPosition = { + start: { line_code: '1' }, + end: { line_code: '1' }, + }; + expect(getCommentedLines(selectedCommentPosition, diffLines)).toEqual({ + startLine: 0, + endLine: 0, + }); + }); + it('returns an object with startLine and endLine equal to 0 and 1', () => { + const selectedCommentPosition = { + start: { line_code: '1' }, + end: { line_code: '2' }, + }; + expect(getCommentedLines(selectedCommentPosition, diffLines)).toEqual({ + startLine: 0, + endLine: 1, + }); + }); + }); }); diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js index 220ac22d8eb..5cc56cdefae 100644 --- a/spec/frontend/notes/components/note_actions_spec.js +++ b/spec/frontend/notes/components/note_actions_spec.js @@ -127,25 +127,63 @@ describe('noteActions', () => { .catch(done.fail); }); - it('should be possible to assign or unassign the comment author', () => { - wrapper = shallowMountNoteActions(props, { - targetType: () => 'issue', - }); - + 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(true); + expect(assignUserButton.exists()).toBe(false); + }); + }); + }); - assignUserButton.trigger('click'); - axiosMock.onPut(`${TEST_HOST}/api/v4/projects/group/project/issues/1`).reply(() => { - expect(actions.updateAssignees).toHaveBeenCalled(); - }); + describe('when a user has access to edit an issue', () => { + const testButtonClickTriggersAction = () => { + 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); + const assignUserButton = wrapper.find('[data-testid="assign-user"]'); + expect(assignUserButton.exists()).toBe(true); + assignUserButton.trigger('click'); + }; + + beforeEach(() => { + wrapper = shallowMountNoteActions(props, { + targetType: () => 'issue', }); + store.state.noteableData = { + current_user: { + can_update: true, + }, + }; + store.state.userData = userDataMock; }); + + afterEach(() => { + wrapper.destroy(); + axiosMock.restore(); + }); + + it('should be possible to assign the comment author', testButtonClickTriggersAction); + it('should be possible to unassign the comment author', testButtonClickTriggersAction); + }); + + describe('when a user does not have access to edit an issue', () => { + const testButtonDoesNotRender = () => { + const assignUserButton = wrapper.find('[data-testid="assign-user"]'); + expect(assignUserButton.exists()).toBe(false); + }; + + beforeEach(() => { + wrapper = shallowMountNoteActions(props, { + targetType: () => 'issue', + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('should not be possible to assign the comment author', testButtonDoesNotRender); + it('should not be possible to unassign the comment author', testButtonDoesNotRender); }); describe('user is not logged in', () => { diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js index 15802841c57..a5b5204509e 100644 --- a/spec/frontend/notes/components/note_form_spec.js +++ b/spec/frontend/notes/components/note_form_spec.js @@ -245,6 +245,24 @@ describe('issue_note_form component', () => { expect(updateDraft).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent); }); + + it('does not save draft when ctrl+enter is pressed', () => { + const options = { + noteBody: '', + autosaveKey: dummyAutosaveKey, + }; + + props = { ...props, ...options }; + wrapper = createComponentWrapper(); + + wrapper.setData({ isSubmittingWithKeydown: true }); + + const textarea = wrapper.find('textarea'); + textarea.setValue('some content'); + textarea.trigger('keydown.enter', { metaKey: true }); + + expect(updateDraft).not.toHaveBeenCalled(); + }); }); describe('with batch comments', () => { diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js index aa3eaa97e20..fc238feb974 100644 --- a/spec/frontend/notes/components/noteable_note_spec.js +++ b/spec/frontend/notes/components/noteable_note_spec.js @@ -34,7 +34,13 @@ describe('issue_note', () => { note, }, localVue, - stubs: ['note-header', 'user-avatar-link', 'note-actions', 'note-body'], + stubs: [ + 'note-header', + 'user-avatar-link', + 'note-actions', + 'note-body', + 'multiline-comment-form', + ], }); }); @@ -46,12 +52,30 @@ describe('issue_note', () => { it('should render if has multiline comment', () => { const position = { line_range: { - start_line_code: 'abc_1_1', - end_line_code: 'abc_2_2', + start: { + line_code: 'abc_1_1', + type: null, + old_line: '1', + new_line: '1', + }, + end: { + line_code: 'abc_2_2', + type: null, + old_line: '2', + new_line: '2', + }, }, }; + const line = { + line_code: 'abc_1_1', + type: null, + old_line: '1', + new_line: '1', + }; wrapper.setProps({ note: { ...note, position }, + discussionRoot: true, + line, }); return wrapper.vm.$nextTick().then(() => { @@ -59,15 +83,51 @@ describe('issue_note', () => { }); }); + it('should render multiline comment if editing discussion root', () => { + wrapper.setProps({ discussionRoot: true }); + wrapper.vm.isEditing = true; + + return wrapper.vm.$nextTick().then(() => { + expect(findMultilineComment().exists()).toBe(true); + }); + }); + + it('should not render multiline comment form unless it is the discussion root', () => { + wrapper.setProps({ discussionRoot: false }); + wrapper.vm.isEditing = true; + + return wrapper.vm.$nextTick().then(() => { + expect(findMultilineComment().exists()).toBe(false); + }); + }); + 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', + start: { + line_code: 'abc_1_1', + type: null, + old_line: '1', + new_line: '1', + }, + end: { + line_code: 'abc_1_1', + type: null, + old_line: '1', + new_line: '1', + }, }, }; + const line = { + line_code: 'abc_1_1', + type: null, + old_line: '1', + new_line: '1', + }; wrapper.setProps({ note: { ...note, position }, + discussionRoot: true, + line, }); return wrapper.vm.$nextTick().then(() => { @@ -139,6 +199,7 @@ describe('issue_note', () => { store.hotUpdate({ actions: { updateNote() {}, + setSelectedCommentPositionHover() {}, }, }); const noteBodyComponent = wrapper.find(NoteBody); diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js index ae30a36fc81..ecff95b6fe0 100644 --- a/spec/frontend/notes/mixins/discussion_navigation_spec.js +++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js @@ -91,6 +91,8 @@ describe('Discussion navigation mixin', () => { beforeEach(() => { window.mrTabs.currentAction = 'show'; wrapper.vm[fn](...args); + + return wrapper.vm.$nextTick(); }); it('sets current discussion', () => { @@ -112,6 +114,8 @@ describe('Discussion navigation mixin', () => { beforeEach(() => { window.mrTabs.currentAction = 'diffs'; wrapper.vm[fn](...args); + + return wrapper.vm.$nextTick(); }); it('sets current discussion', () => { @@ -137,6 +141,8 @@ describe('Discussion navigation mixin', () => { beforeEach(() => { window.mrTabs.currentAction = 'other'; wrapper.vm[fn](...args); + + return wrapper.vm.$nextTick(); }); it('sets current discussion', () => { diff --git a/spec/frontend/notes/old_notes_spec.js b/spec/frontend/notes/old_notes_spec.js index cb1d563ece7..dee4f93f0ce 100644 --- a/spec/frontend/notes/old_notes_spec.js +++ b/spec/frontend/notes/old_notes_spec.js @@ -624,7 +624,7 @@ describe.skip('Old Notes (~/notes.js)', () => { }); }); - describe('postComment with Slash commands', () => { + describe('postComment with quick actions', () => { const sampleComment = '/assign @root\n/award :100:'; const note = { commands_changes: { @@ -640,6 +640,7 @@ describe.skip('Old Notes (~/notes.js)', () => { let $notesContainer; beforeEach(() => { + loadFixtures('commit/show.html'); mockAxios.onPost(NOTES_POST_PATH).reply(200, note); new Notes('', []); @@ -659,14 +660,49 @@ describe.skip('Old Notes (~/notes.js)', () => { $form.find('textarea.js-note-text').val(sampleComment); }); - it('should remove slash command placeholder when comment with slash commands is done posting', done => { + it('should remove quick action placeholder when comment with quick actions is done posting', done => { jest.spyOn(gl.awardsHandler, 'addAwardToEmojiBar'); $('.js-comment-button').click(); - expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown + expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown setImmediate(() => { - expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed + expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed + done(); + }); + }); + }); + + describe('postComment with slash when quick actions are not supported', () => { + const sampleComment = '/assign @root'; + let $form; + let $notesContainer; + + beforeEach(() => { + const note = { + id: 1234, + html: `<li class="note note-row-1234 timeline-entry" id="note_1234"> + <div class="note-text">${sampleComment}</div> + </li>`, + note: sampleComment, + valid: true, + }; + mockAxios.onPost(NOTES_POST_PATH).reply(200, note); + + new Notes('', []); + $form = $('form.js-main-target-form'); + $notesContainer = $('ul.main-notes-list'); + $form.find('textarea.js-note-text').val(sampleComment); + }); + + it('should show message placeholder including lines starting with slash', done => { + $('.js-comment-button').click(); + + expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown + expect($notesContainer.find('.note-body p').text()).toEqual(sampleComment); // No quick action processing + + setImmediate(() => { + expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed done(); }); }); diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js index ef87cb3bee7..909a4a797ae 100644 --- a/spec/frontend/notes/stores/actions_spec.js +++ b/spec/frontend/notes/stores/actions_spec.js @@ -18,6 +18,8 @@ import { batchSuggestionsInfoMock, } from '../mock_data'; import axios from '~/lib/utils/axios_utils'; +import * as utils from '~/notes/stores/utils'; +import updateIssueConfidentialMutation from '~/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql'; const TEST_ERROR_MESSAGE = 'Test error message'; jest.mock('~/flash'); @@ -272,9 +274,54 @@ describe('Actions Notes Store', () => { }); }); + 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' }, + ], + )); + }); + }); + describe('poll', () => { beforeEach(done => { - jest.spyOn(axios, 'get'); + axiosMock + .onGet(notesDataMock.notesPath) + .reply(200, { notes: [], last_fetched_at: '123456' }, { 'poll-interval': '1000' }); store .dispatch('setNotesData', notesDataMock) @@ -283,15 +330,10 @@ describe('Actions Notes Store', () => { }); 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); @@ -303,8 +345,9 @@ describe('Actions Notes Store', () => { }), ) .then(() => { - expect(axios.get.mock.calls.length).toBe(2); - expect(axios.get.mock.calls[axios.get.mock.calls.length - 1][1].headers).toEqual({ + const expectedGetRequests = 2; + expect(axiosMock.history.get.length).toBe(expectedGetRequests); + expect(axiosMock.history.get[expectedGetRequests - 1].headers).toMatchObject({ 'X-Last-Fetched-At': '123456', }); }) @@ -449,7 +492,7 @@ describe('Actions Notes Store', () => { it('commits ADD_NEW_NOTE and dispatches updateMergeRequestWidget', done => { testAction( actions.createNewNote, - { endpoint: `${gl.TEST_HOST}`, data: {} }, + { endpoint: `${TEST_HOST}`, data: {} }, store.state, [ { @@ -485,7 +528,7 @@ describe('Actions Notes Store', () => { it('does not commit ADD_NEW_NOTE or dispatch updateMergeRequestWidget', done => { testAction( actions.createNewNote, - { endpoint: `${gl.TEST_HOST}`, data: {} }, + { endpoint: `${TEST_HOST}`, data: {} }, store.state, [], [], @@ -508,7 +551,7 @@ describe('Actions Notes Store', () => { it('commits UPDATE_NOTE and dispatches updateMergeRequestWidget', done => { testAction( actions.toggleResolveNote, - { endpoint: `${gl.TEST_HOST}`, isResolved: true, discussion: false }, + { endpoint: `${TEST_HOST}`, isResolved: true, discussion: false }, store.state, [ { @@ -533,7 +576,7 @@ describe('Actions Notes Store', () => { it('commits UPDATE_DISCUSSION and dispatches updateMergeRequestWidget', done => { testAction( actions.toggleResolveNote, - { endpoint: `${gl.TEST_HOST}`, isResolved: true, discussion: true }, + { endpoint: `${TEST_HOST}`, isResolved: true, discussion: true }, store.state, [ { @@ -1084,6 +1127,19 @@ describe('Actions Notes Store', () => { }); }); + describe('setSelectedCommentPosition', () => { + it('calls the correct mutation with the correct args', done => { + testAction( + actions.setSelectedCommentPosition, + {}, + {}, + [{ type: mutationTypes.SET_SELECTED_COMMENT_POSITION, payload: {} }], + [], + done, + ); + }); + }); + describe('softDeleteDescriptionVersion', () => { const endpoint = '/path/to/diff/1'; const payload = { @@ -1142,6 +1198,14 @@ describe('Actions Notes Store', () => { }); }); + 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 }, + ]); + }); + }); + describe('updateAssignees', () => { it('update the assignees state', done => { testAction( @@ -1154,4 +1218,49 @@ describe('Actions Notes Store', () => { ); }); }); + + describe('updateConfidentialityOnIssue', () => { + 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', () => { + actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs); + + expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1); + }); + + it('calls gqClient mutation with the correct values', () => { + actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs); + + 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 + .updateConfidentialityOnIssue({ commit: commitSpy, state, getters }, actionArgs) + .then(() => { + expect(commitSpy).toHaveBeenCalledWith( + mutationTypes.SET_ISSUE_CONFIDENTIAL, + confidential, + ); + }); + }); + }); + }); }); diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js index 75ef007b78d..0ad18ba9b6a 100644 --- a/spec/frontend/notes/stores/mutation_spec.js +++ b/spec/frontend/notes/stores/mutation_spec.js @@ -524,6 +524,26 @@ describe('Notes Store mutations', () => { }); }); + describe('SET_SELECTED_COMMENT_POSITION', () => { + it('should set comment position state', () => { + const state = {}; + + mutations.SET_SELECTED_COMMENT_POSITION(state, {}); + + expect(state.selectedCommentPosition).toEqual({}); + }); + }); + + describe('SET_SELECTED_COMMENT_POSITION_HOVER', () => { + it('should set comment hover position state', () => { + const state = {}; + + mutations.SET_SELECTED_COMMENT_POSITION_HOVER(state, {}); + + expect(state.selectedCommentPositionHover).toEqual({}); + }); + }); + describe('DISABLE_COMMENTS', () => { it('should set comments disabled state', () => { const state = {}; @@ -806,6 +826,20 @@ describe('Notes Store mutations', () => { }); }); + describe('SET_ISSUE_CONFIDENTIAL', () => { + let state; + + beforeEach(() => { + state = { noteableData: { confidential: false } }; + }); + + it('sets sort order', () => { + mutations.SET_ISSUE_CONFIDENTIAL(state, true); + + expect(state.noteableData.confidential).toBe(true); + }); + }); + describe('UPDATE_ASSIGNEES', () => { it('should update assignees', () => { const state = { |