diff options
Diffstat (limited to 'spec/javascripts')
43 files changed, 1372 insertions, 320 deletions
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js index 81f1a97112f..f380ef450db 100644 --- a/spec/javascripts/boards/mock_data.js +++ b/spec/javascripts/boards/mock_data.js @@ -27,7 +27,7 @@ export const listObjDuplicate = { export const BoardsMockData = { GET: { - '/test/-/boards/1/lists/300/issues?id=300&page=1&=': { + '/test/-/boards/1/lists/300/issues?id=300&page=1': { issues: [ { title: 'Testing', diff --git a/spec/javascripts/boards/utils/query_data_spec.js b/spec/javascripts/boards/utils/query_data_spec.js deleted file mode 100644 index 922215ffc1d..00000000000 --- a/spec/javascripts/boards/utils/query_data_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import queryData from '~/boards/utils/query_data'; - -describe('queryData', () => { - it('parses path for label with trailing +', () => { - expect( - queryData('label_name[]=label%2B', {}), - ).toEqual({ - label_name: ['label+'], - }); - }); - - it('parses path for milestone with trailing +', () => { - expect( - queryData('milestone_title=A%2B', {}), - ).toEqual({ - milestone_title: 'A+', - }); - }); - - it('parses path for search terms with spaces', () => { - expect( - queryData('search=two+words', {}), - ).toEqual({ - search: 'two words', - }); - }); -}); diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js index 44a38f7ca82..845fef23db6 100644 --- a/spec/javascripts/diffs/components/diff_file_spec.js +++ b/spec/javascripts/diffs/components/diff_file_spec.js @@ -51,7 +51,9 @@ describe('DiffFile', () => { }); it('should have collapsed text and link', done => { - vm.file.collapsed = true; + vm.file.renderIt = true; + vm.file.collapsed = false; + vm.file.highlightedDiffLines = null; vm.$nextTick(() => { expect(vm.$el.innerText).toContain('This diff is collapsed'); diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js index a1a37b342b7..663c0680845 100644 --- a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js +++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js @@ -6,61 +6,61 @@ import discussionsMockData from '../mock_data/diff_discussions'; import diffFileMockData from '../mock_data/diff_file'; describe('DiffLineGutterContent', () => { - const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; const getDiffFileMock = () => Object.assign({}, diffFileMockData); const createComponent = (options = {}) => { const cmp = Vue.extend(DiffLineGutterContent); const props = Object.assign({}, options); + props.line = { + lineCode: 'LC_42', + type: 'new', + oldLine: null, + newLine: 1, + discussions: [], + text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + richText: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + metaData: null, + }; props.fileHash = getDiffFileMock().fileHash; props.contextLinesPath = '/context/lines/path'; return createComponentWithStore(cmp, store, props).$mount(); }; - const setDiscussions = component => { - component.$store.dispatch('setInitialNotes', getDiscussionsMockData()); - }; - - const resetDiscussions = component => { - component.$store.dispatch('setInitialNotes', []); - }; describe('computed', () => { describe('lineHref', () => { it('should prepend # to lineCode', () => { const lineCode = 'LC_42'; - const component = createComponent({ lineCode }); + const component = createComponent(); expect(component.lineHref).toEqual(`#${lineCode}`); }); it('should return # if there is no lineCode', () => { - const component = createComponent({ lineCode: null }); + const component = createComponent(); + component.line.lineCode = ''; expect(component.lineHref).toEqual('#'); }); }); describe('discussions, hasDiscussions, shouldShowAvatarsOnGutter', () => { it('should return empty array when there is no discussion', () => { - const component = createComponent({ lineCode: 'LC_42' }); - expect(component.discussions).toEqual([]); + const component = createComponent(); expect(component.hasDiscussions).toEqual(false); expect(component.shouldShowAvatarsOnGutter).toEqual(false); }); it('should return discussions for the given lineCode', () => { - const { lineCode } = getDiffFileMock().highlightedDiffLines[1]; - const component = createComponent({ - lineCode, + const cmp = Vue.extend(DiffLineGutterContent); + const props = { + line: getDiffFileMock().highlightedDiffLines[1], + fileHash: getDiffFileMock().fileHash, showCommentButton: true, - discussions: getDiscussionsMockData(), - }); + contextLinesPath: '/context/lines/path', + }; + props.line.discussions = [Object.assign({}, discussionsMockData)]; + const component = createComponentWithStore(cmp, store, props).$mount(); - setDiscussions(component); - - expect(component.discussions).toEqual(getDiscussionsMockData()); expect(component.hasDiscussions).toEqual(true); expect(component.shouldShowAvatarsOnGutter).toEqual(true); - - resetDiscussions(component); }); }); }); @@ -104,9 +104,7 @@ describe('DiffLineGutterContent', () => { lineCode: getDiffFileMock().highlightedDiffLines[1].lineCode, }); - setDiscussions(component); expect(component.$el.querySelector('.diff-comment-avatar-holders')).toBeDefined(); - resetDiscussions(component); }); }); }); diff --git a/spec/javascripts/diffs/components/parallel_diff_view_spec.js b/spec/javascripts/diffs/components/parallel_diff_view_spec.js index 165e4b69b6c..091e01868d3 100644 --- a/spec/javascripts/diffs/components/parallel_diff_view_spec.js +++ b/spec/javascripts/diffs/components/parallel_diff_view_spec.js @@ -18,11 +18,11 @@ describe('ParallelDiffView', () => { }).$mount(); }); - describe('computed', () => { - describe('parallelDiffLines', () => { + describe('assigned', () => { + describe('diffLines', () => { it('should normalize lines for empty cells', () => { - expect(component.parallelDiffLines[0].left.type).toEqual(constants.EMPTY_CELL_TYPE); - expect(component.parallelDiffLines[1].left.type).toEqual(constants.EMPTY_CELL_TYPE); + expect(component.diffLines[0].left.type).toEqual(constants.EMPTY_CELL_TYPE); + expect(component.diffLines[1].left.type).toEqual(constants.EMPTY_CELL_TYPE); }); }); }); diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index 41d0dfd8939..b29a22da7c2 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -16,7 +16,7 @@ export default { expanded: true, notes: [ { - id: 1749, + id: '1749', type: 'DiffNote', attachment: null, author: { @@ -68,7 +68,7 @@ export default { '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', }, { - id: 1753, + id: '1753', type: 'DiffNote', attachment: null, author: { @@ -120,7 +120,7 @@ export default { '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', }, { - id: 1754, + id: '1754', type: 'DiffNote', attachment: null, author: { @@ -162,7 +162,7 @@ export default { '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', }, { - id: 1755, + id: '1755', type: 'DiffNote', attachment: null, author: { @@ -204,7 +204,7 @@ export default { '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', }, { - id: 1756, + id: '1756', type: 'DiffNote', attachment: null, author: { diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js index cce36ecc91f..372b8f066cf 100644 --- a/spec/javascripts/diffs/mock_data/diff_file.js +++ b/spec/javascripts/diffs/mock_data/diff_file.js @@ -49,6 +49,7 @@ export default { type: 'new', oldLine: null, newLine: 1, + discussions: [], text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', richText: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', metaData: null, @@ -58,6 +59,7 @@ export default { type: 'new', oldLine: null, newLine: 2, + discussions: [], text: '+<span id="LC2" class="line" lang="plaintext"></span>\n', richText: '+<span id="LC2" class="line" lang="plaintext"></span>\n', metaData: null, @@ -67,6 +69,7 @@ export default { type: null, oldLine: 1, newLine: 3, + discussions: [], text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', richText: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', metaData: null, @@ -76,6 +79,7 @@ export default { type: null, oldLine: 2, newLine: 4, + discussions: [], text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', richText: ' <span id="LC4" class="line" lang="plaintext"></span>\n', metaData: null, @@ -85,6 +89,7 @@ export default { type: null, oldLine: 3, newLine: 5, + discussions: [], text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', richText: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', metaData: null, @@ -94,6 +99,7 @@ export default { type: 'match', oldLine: null, newLine: null, + discussions: [], text: '', richText: '', metaData: { @@ -112,6 +118,7 @@ export default { type: 'new', oldLine: null, newLine: 1, + discussions: [], text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', richText: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', metaData: null, @@ -126,6 +133,7 @@ export default { type: 'new', oldLine: null, newLine: 2, + discussions: [], text: '+<span id="LC2" class="line" lang="plaintext"></span>\n', richText: '<span id="LC2" class="line" lang="plaintext"></span>\n', metaData: null, @@ -137,6 +145,7 @@ export default { type: null, oldLine: 1, newLine: 3, + discussions: [], text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', metaData: null, @@ -146,6 +155,7 @@ export default { type: null, oldLine: 1, newLine: 3, + discussions: [], text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', metaData: null, @@ -157,6 +167,7 @@ export default { type: null, oldLine: 2, newLine: 4, + discussions: [], text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', richText: '<span id="LC4" class="line" lang="plaintext"></span>\n', metaData: null, @@ -166,6 +177,7 @@ export default { type: null, oldLine: 2, newLine: 4, + discussions: [], text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', richText: '<span id="LC4" class="line" lang="plaintext"></span>\n', metaData: null, @@ -177,6 +189,7 @@ export default { type: null, oldLine: 3, newLine: 5, + discussions: [], text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', metaData: null, @@ -186,6 +199,7 @@ export default { type: null, oldLine: 3, newLine: 5, + discussions: [], text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', metaData: null, @@ -197,6 +211,7 @@ export default { type: 'match', oldLine: null, newLine: null, + discussions: [], text: '', richText: '', metaData: { @@ -209,6 +224,7 @@ export default { type: 'match', oldLine: null, newLine: null, + discussions: [], text: '', richText: '', metaData: { diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index c1560dac1a0..cfb8f862598 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -7,10 +7,30 @@ import { } from '~/diffs/constants'; import * as actions from '~/diffs/store/actions'; import * as types from '~/diffs/store/mutation_types'; +import { reduceDiscussionsToLineCodes } from '~/notes/stores/utils'; import axios from '~/lib/utils/axios_utils'; import testAction from '../../helpers/vuex_action_helper'; describe('DiffsStoreActions', () => { + const originalMethods = { + requestAnimationFrame: global.requestAnimationFrame, + requestIdleCallback: global.requestIdleCallback, + }; + + beforeEach(() => { + ['requestAnimationFrame', 'requestIdleCallback'].forEach(method => { + global[method] = cb => { + cb(); + }; + }); + }); + + afterEach(() => { + ['requestAnimationFrame', 'requestIdleCallback'].forEach(method => { + global[method] = originalMethods[method]; + }); + }); + describe('setBaseConfig', () => { it('should set given endpoint and project path', done => { const endpoint = '/diffs/set/endpoint'; @@ -53,6 +73,198 @@ describe('DiffsStoreActions', () => { }); }); + describe('assignDiscussionsToDiff', () => { + it('should merge discussions into diffs', done => { + const state = { + diffFiles: [ + { + fileHash: 'ABC', + parallelDiffLines: [ + { + left: { + lineCode: 'ABC_1_1', + discussions: [], + }, + right: { + lineCode: 'ABC_1_1', + discussions: [], + }, + }, + ], + highlightedDiffLines: [ + { + lineCode: 'ABC_1_1', + discussions: [], + oldLine: 5, + newLine: null, + }, + ], + diffRefs: { + baseSha: 'abc', + headSha: 'def', + startSha: 'ghi', + }, + newPath: 'file1', + oldPath: 'file2', + }, + ], + }; + + const diffPosition = { + baseSha: 'abc', + headSha: 'def', + startSha: 'ghi', + newLine: null, + newPath: 'file1', + oldLine: 5, + oldPath: 'file2', + }; + + const singleDiscussion = { + line_code: 'ABC_1_1', + diff_discussion: {}, + diff_file: { + file_hash: 'ABC', + }, + fileHash: 'ABC', + resolvable: true, + position: { + formatter: diffPosition, + }, + original_position: { + formatter: diffPosition, + }, + }; + + const discussions = reduceDiscussionsToLineCodes([singleDiscussion]); + + testAction( + actions.assignDiscussionsToDiff, + discussions, + state, + [ + { + type: types.SET_LINE_DISCUSSIONS_FOR_FILE, + payload: { + fileHash: 'ABC', + discussions: [singleDiscussion], + diffPositionByLineCode: { + ABC_1_1: { + baseSha: 'abc', + headSha: 'def', + startSha: 'ghi', + newLine: null, + newPath: 'file1', + oldLine: 5, + oldPath: 'file2', + }, + }, + }, + }, + ], + [], + () => { + done(); + }, + ); + }); + }); + + describe('removeDiscussionsFromDiff', () => { + it('should remove discussions from diffs', done => { + const state = { + diffFiles: [ + { + fileHash: 'ABC', + parallelDiffLines: [ + { + left: { + lineCode: 'ABC_1_1', + discussions: [ + { + id: 1, + }, + ], + }, + right: { + lineCode: 'ABC_1_1', + discussions: [], + }, + }, + ], + highlightedDiffLines: [ + { + lineCode: 'ABC_1_1', + discussions: [], + }, + ], + }, + ], + }; + const singleDiscussion = { + fileHash: 'ABC', + line_code: 'ABC_1_1', + }; + + testAction( + actions.removeDiscussionsFromDiff, + singleDiscussion, + state, + [ + { + type: types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, + payload: { + fileHash: 'ABC', + lineCode: 'ABC_1_1', + }, + }, + ], + [], + () => { + done(); + }, + ); + }); + }); + + describe('startRenderDiffsQueue', () => { + it('should set all files to RENDER_FILE', done => { + const state = { + diffFiles: [ + { + id: 1, + renderIt: false, + collapsed: false, + }, + { + id: 2, + renderIt: false, + collapsed: false, + }, + ], + }; + + const pseudoCommit = (commitType, file) => { + expect(commitType).toBe(types.RENDER_FILE); + Object.assign(file, { + renderIt: true, + }); + }; + + actions + .startRenderDiffsQueue({ state, commit: pseudoCommit }) + .then(() => { + expect(state.diffFiles[0].renderIt).toBeTruthy(); + expect(state.diffFiles[1].renderIt).toBeTruthy(); + + done(); + }) + .catch(() => { + done.fail(); + }); + }); + }); + describe('setInlineDiffViewType', () => { it('should set diff view type to inline and also set the cookie properly', done => { testAction( @@ -204,7 +416,11 @@ describe('DiffsStoreActions', () => { actions.toggleFileDiscussions({ getters, dispatch }); - expect(dispatch).toHaveBeenCalledWith('collapseDiscussion', { discussionId: 1 }, { root: true }); + expect(dispatch).toHaveBeenCalledWith( + 'collapseDiscussion', + { discussionId: 1 }, + { root: true }, + ); }); it('should dispatch expandDiscussion when all discussions are collapsed', () => { @@ -218,7 +434,11 @@ describe('DiffsStoreActions', () => { actions.toggleFileDiscussions({ getters, dispatch }); - expect(dispatch).toHaveBeenCalledWith('expandDiscussion', { discussionId: 1 }, { root: true }); + expect(dispatch).toHaveBeenCalledWith( + 'expandDiscussion', + { discussionId: 1 }, + { root: true }, + ); }); it('should dispatch expandDiscussion when some discussions are collapsed and others are expanded for the collapsed discussion', () => { @@ -232,7 +452,11 @@ describe('DiffsStoreActions', () => { actions.toggleFileDiscussions({ getters, dispatch }); - expect(dispatch).toHaveBeenCalledWith('expandDiscussion', { discussionId: 1 }, { root: true }); + expect(dispatch).toHaveBeenCalledWith( + 'expandDiscussion', + { discussionId: 1 }, + { root: true }, + ); }); }); }); diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js index a59b26b2634..4747e437c4e 100644 --- a/spec/javascripts/diffs/store/getters_spec.js +++ b/spec/javascripts/diffs/store/getters_spec.js @@ -184,101 +184,73 @@ describe('Diffs Module Getters', () => { }); }); - describe('singleDiscussionByLineCode', () => { - it('returns found discussion per line Code', () => { - const discussionsMock = {}; - discussionsMock.ABC = discussionMock; - - expect( - getters.singleDiscussionByLineCode(localState, {}, null, { - discussionsByLineCode: () => discussionsMock, - })('DEF'), - ).toEqual([]); - }); - - it('returns empty array when no discussions match', () => { - expect( - getters.singleDiscussionByLineCode(localState, {}, null, { - discussionsByLineCode: () => {}, - })('DEF'), - ).toEqual([]); - }); - }); - describe('shouldRenderParallelCommentRow', () => { let line; beforeEach(() => { line = {}; + discussionMock.expanded = true; + line.left = { lineCode: 'ABC', + discussions: [discussionMock], }; line.right = { lineCode: 'DEF', + discussions: [discussionMock1], }; }); it('returns true when discussion is expanded', () => { - discussionMock.expanded = true; - - expect( - getters.shouldRenderParallelCommentRow(localState, { - singleDiscussionByLineCode: () => [discussionMock], - })(line), - ).toEqual(true); + expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true); }); it('returns false when no discussion was found', () => { + line.left.discussions = []; + line.right.discussions = []; + localState.diffLineCommentForms.ABC = false; localState.diffLineCommentForms.DEF = false; - expect( - getters.shouldRenderParallelCommentRow(localState, { - singleDiscussionByLineCode: () => [], - })(line), - ).toEqual(false); + expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(false); }); it('returns true when discussionForm was found', () => { localState.diffLineCommentForms.ABC = {}; - expect( - getters.shouldRenderParallelCommentRow(localState, { - singleDiscussionByLineCode: () => [discussionMock], - })(line), - ).toEqual(true); + expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true); }); }); describe('shouldRenderInlineCommentRow', () => { + let line; + + beforeEach(() => { + discussionMock.expanded = true; + + line = { + lineCode: 'ABC', + discussions: [discussionMock], + }; + }); + it('returns true when diffLineCommentForms has form', () => { localState.diffLineCommentForms.ABC = {}; - expect( - getters.shouldRenderInlineCommentRow(localState)({ - lineCode: 'ABC', - }), - ).toEqual(true); + expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true); }); it('returns false when no line discussions were found', () => { - expect( - getters.shouldRenderInlineCommentRow(localState, { - singleDiscussionByLineCode: () => [], - })('DEF'), - ).toEqual(false); + line.discussions = []; + expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(false); }); it('returns true if all found discussions are expanded', () => { discussionMock.expanded = true; - expect( - getters.shouldRenderInlineCommentRow(localState, { - singleDiscussionByLineCode: () => [discussionMock], - })('ABC'), - ).toEqual(true); + expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true); }); }); diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index 8f89984c6e5..7eeca6712cc 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -138,10 +138,9 @@ describe('DiffsStoreMutations', () => { const fileHash = 123; const state = { diffFiles: [{}, { fileHash, existingField: 0 }] }; - const file = { fileHash }; const data = { diff_files: [{ file_hash: fileHash, extra_field: 1, existingField: 1 }] }; - mutations[types.ADD_COLLAPSED_DIFFS](state, { file, data }); + mutations[types.ADD_COLLAPSED_DIFFS](state, { file: state.diffFiles[1], data }); expect(spy).toHaveBeenCalledWith(data, { deep: true }); expect(state.diffFiles[1].fileHash).toEqual(fileHash); @@ -149,4 +148,141 @@ describe('DiffsStoreMutations', () => { expect(state.diffFiles[1].extraField).toEqual(1); }); }); + + describe('SET_LINE_DISCUSSIONS_FOR_FILE', () => { + it('should add discussions to the given line', () => { + const diffPosition = { + baseSha: 'ed13df29948c41ba367caa757ab3ec4892509910', + headSha: 'b921914f9a834ac47e6fd9420f78db0f83559130', + newLine: null, + newPath: '500-lines-4.txt', + oldLine: 5, + oldPath: '500-lines-4.txt', + startSha: 'ed13df29948c41ba367caa757ab3ec4892509910', + }; + + const state = { + diffFiles: [ + { + fileHash: 'ABC', + parallelDiffLines: [ + { + left: { + lineCode: 'ABC_1', + discussions: [], + }, + right: { + lineCode: 'ABC_1', + discussions: [], + }, + }, + ], + highlightedDiffLines: [ + { + lineCode: 'ABC_1', + discussions: [], + }, + ], + }, + ], + }; + const discussions = [ + { + id: 1, + line_code: 'ABC_1', + diff_discussion: true, + resolvable: true, + original_position: { + formatter: diffPosition, + }, + position: { + formatter: diffPosition, + }, + }, + { + id: 2, + line_code: 'ABC_1', + diff_discussion: true, + resolvable: true, + original_position: { + formatter: diffPosition, + }, + position: { + formatter: diffPosition, + }, + }, + ]; + + const diffPositionByLineCode = { + ABC_1: diffPosition, + }; + + mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { + fileHash: 'ABC', + discussions, + diffPositionByLineCode, + }); + + expect(state.diffFiles[0].parallelDiffLines[0].left.discussions.length).toEqual(2); + expect(state.diffFiles[0].parallelDiffLines[0].left.discussions[1].id).toEqual(2); + + expect(state.diffFiles[0].highlightedDiffLines[0].discussions.length).toEqual(2); + expect(state.diffFiles[0].highlightedDiffLines[0].discussions[1].id).toEqual(2); + }); + }); + + describe('REMOVE_LINE_DISCUSSIONS', () => { + it('should remove the existing discussions on the given line', () => { + const state = { + diffFiles: [ + { + fileHash: 'ABC', + parallelDiffLines: [ + { + left: { + lineCode: 'ABC_1', + discussions: [ + { + id: 1, + line_code: 'ABC_1', + }, + { + id: 2, + line_code: 'ABC_1', + }, + ], + }, + right: { + lineCode: 'ABC_1', + discussions: [], + }, + }, + ], + highlightedDiffLines: [ + { + lineCode: 'ABC_1', + discussions: [ + { + id: 1, + line_code: 'ABC_1', + }, + { + id: 2, + line_code: 'ABC_1', + }, + ], + }, + ], + }, + ], + }; + + mutations[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { + fileHash: 'ABC', + lineCode: 'ABC_1', + }); + expect(state.diffFiles[0].parallelDiffLines[0].left.discussions.length).toEqual(0); + expect(state.diffFiles[0].highlightedDiffLines[0].discussions.length).toEqual(0); + }); + }); }); diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js index 32136d9ebff..4b5cf450c68 100644 --- a/spec/javascripts/diffs/store/utils_spec.js +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -179,32 +179,126 @@ describe('DiffsStoreUtils', () => { describe('trimFirstCharOfLineContent', () => { it('trims the line when it starts with a space', () => { - expect(utils.trimFirstCharOfLineContent({ richText: ' diff' })).toEqual({ richText: 'diff' }); + expect(utils.trimFirstCharOfLineContent({ richText: ' diff' })).toEqual({ + discussions: [], + richText: 'diff', + }); }); it('trims the line when it starts with a +', () => { - expect(utils.trimFirstCharOfLineContent({ richText: '+diff' })).toEqual({ richText: 'diff' }); + expect(utils.trimFirstCharOfLineContent({ richText: '+diff' })).toEqual({ + discussions: [], + richText: 'diff', + }); }); it('trims the line when it starts with a -', () => { - expect(utils.trimFirstCharOfLineContent({ richText: '-diff' })).toEqual({ richText: 'diff' }); + expect(utils.trimFirstCharOfLineContent({ richText: '-diff' })).toEqual({ + discussions: [], + richText: 'diff', + }); }); it('does not trims the line when it starts with a letter', () => { - expect(utils.trimFirstCharOfLineContent({ richText: 'diff' })).toEqual({ richText: 'diff' }); + expect(utils.trimFirstCharOfLineContent({ richText: 'diff' })).toEqual({ + discussions: [], + richText: 'diff', + }); }); it('does not modify the provided object', () => { const lineObj = { + discussions: [], richText: ' diff', }; utils.trimFirstCharOfLineContent(lineObj); - expect(lineObj).toEqual({ richText: ' diff' }); + expect(lineObj).toEqual({ discussions: [], richText: ' diff' }); }); it('handles a undefined or null parameter', () => { - expect(utils.trimFirstCharOfLineContent()).toEqual({}); + expect(utils.trimFirstCharOfLineContent()).toEqual({ discussions: [] }); + }); + }); + + describe('prepareDiffData', () => { + it('sets the renderIt and collapsed attribute on files', () => { + const preparedDiff = { diffFiles: [getDiffFileMock()] }; + utils.prepareDiffData(preparedDiff); + + const firstParallelDiffLine = preparedDiff.diffFiles[0].parallelDiffLines[2]; + expect(firstParallelDiffLine.left.discussions.length).toBe(0); + expect(firstParallelDiffLine.left).not.toHaveAttr('text'); + expect(firstParallelDiffLine.right.discussions.length).toBe(0); + expect(firstParallelDiffLine.right).not.toHaveAttr('text'); + const firstParallelChar = firstParallelDiffLine.right.richText.charAt(0); + expect(firstParallelChar).not.toBe(' '); + expect(firstParallelChar).not.toBe('+'); + expect(firstParallelChar).not.toBe('-'); + + const checkLine = preparedDiff.diffFiles[0].highlightedDiffLines[0]; + expect(checkLine.discussions.length).toBe(0); + expect(checkLine).not.toHaveAttr('text'); + const firstChar = checkLine.richText.charAt(0); + expect(firstChar).not.toBe(' '); + expect(firstChar).not.toBe('+'); + expect(firstChar).not.toBe('-'); + + expect(preparedDiff.diffFiles[0].renderIt).toBeTruthy(); + expect(preparedDiff.diffFiles[0].collapsed).toBeFalsy(); + }); + }); + + describe('isDiscussionApplicableToLine', () => { + const diffPosition = { + baseSha: 'ed13df29948c41ba367caa757ab3ec4892509910', + headSha: 'b921914f9a834ac47e6fd9420f78db0f83559130', + newLine: null, + newPath: '500-lines-4.txt', + oldLine: 5, + oldPath: '500-lines-4.txt', + startSha: 'ed13df29948c41ba367caa757ab3ec4892509910', + }; + + const wrongDiffPosition = { + baseSha: 'wrong', + headSha: 'wrong', + newLine: null, + newPath: '500-lines-4.txt', + oldLine: 5, + oldPath: '500-lines-4.txt', + startSha: 'wrong', + }; + + const discussions = { + upToDateDiscussion1: { + original_position: { + formatter: diffPosition, + }, + position: { + formatter: wrongDiffPosition, + }, + }, + outDatedDiscussion1: { + original_position: { + formatter: wrongDiffPosition, + }, + position: { + formatter: wrongDiffPosition, + }, + }, + }; + + it('returns true when the discussion is up to date', () => { + expect( + utils.isDiscussionApplicableToLine(discussions.upToDateDiscussion1, diffPosition), + ).toBe(true); + }); + + it('returns false when the discussion is not up to date', () => { + expect( + utils.isDiscussionApplicableToLine(discussions.outDatedDiscussion1, diffPosition), + ).toBe(false); }); }); }); diff --git a/spec/javascripts/dropzone_input_spec.js b/spec/javascripts/dropzone_input_spec.js new file mode 100644 index 00000000000..0c6b1a8946d --- /dev/null +++ b/spec/javascripts/dropzone_input_spec.js @@ -0,0 +1,68 @@ +import $ from 'jquery'; +import dropzoneInput from '~/dropzone_input'; +import { TEST_HOST } from 'spec/test_constants'; + +const TEST_FILE = { + upload: {}, +}; +const TEST_UPLOAD_PATH = `${TEST_HOST}/upload/file`; +const TEST_ERROR_MESSAGE = 'A big error occurred!'; +const TEMPLATE = ( +`<form class="gfm-form" data-uploads-path="${TEST_UPLOAD_PATH}"> + <textarea class="js-gfm-input"></textarea> + <div class="uploading-error-message"></div> +</form>` +); + +describe('dropzone_input', () => { + let form; + let dropzone; + let xhr; + let oldXMLHttpRequest; + + beforeEach(() => { + form = $(TEMPLATE); + + dropzone = dropzoneInput(form); + + xhr = jasmine.createSpyObj(Object.keys(XMLHttpRequest.prototype)); + oldXMLHttpRequest = window.XMLHttpRequest; + window.XMLHttpRequest = () => xhr; + }); + + afterEach(() => { + window.XMLHttpRequest = oldXMLHttpRequest; + }); + + it('shows error message, when AJAX fails with json', () => { + xhr = { + ...xhr, + statusCode: 400, + readyState: 4, + responseText: JSON.stringify({ message: TEST_ERROR_MESSAGE }), + getResponseHeader: () => 'application/json', + }; + + dropzone.processFile(TEST_FILE); + + xhr.onload(); + + expect(form.find('.uploading-error-message').text()).toEqual(TEST_ERROR_MESSAGE); + }); + + it('shows error message, when AJAX fails with text', () => { + xhr = { + ...xhr, + statusCode: 400, + readyState: 4, + responseText: TEST_ERROR_MESSAGE, + getResponseHeader: () => 'text/plain', + }; + + dropzone.processFile(TEST_FILE); + + xhr.onload(); + + expect(form.find('.uploading-error-message').text()).toEqual(TEST_ERROR_MESSAGE); + }); +}); diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js index 1cb20a1e7ff..4f9cacf2724 100644 --- a/spec/javascripts/gfm_auto_complete_spec.js +++ b/spec/javascripts/gfm_auto_complete_spec.js @@ -146,7 +146,7 @@ describe('GfmAutoComplete', function () { shouldNotBeFollowedBy.forEach((followedSymbol) => { const seq = atSign + followedSymbol; - it(`should not match "${seq}"`, () => { + it(`should not match ${JSON.stringify(seq)}`, () => { expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null); }); }); diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js index 03d4b472b87..89c07d1f06d 100644 --- a/spec/javascripts/groups/components/app_spec.js +++ b/spec/javascripts/groups/components/app_spec.js @@ -9,9 +9,14 @@ import GroupsStore from '~/groups/store/groups_store'; import GroupsService from '~/groups/service/groups_service'; import { - mockEndpoint, mockGroups, mockSearchedGroups, - mockRawPageInfo, mockParentGroupItem, mockRawChildren, - mockChildren, mockPageInfo, + mockEndpoint, + mockGroups, + mockSearchedGroups, + mockRawPageInfo, + mockParentGroupItem, + mockRawChildren, + mockChildren, + mockPageInfo, } from '../mock_data'; const createComponent = (hideProjects = false) => { @@ -19,6 +24,8 @@ const createComponent = (hideProjects = false) => { const store = new GroupsStore(false); const service = new GroupsService(mockEndpoint); + store.state.pageInfo = mockPageInfo; + return new Component({ propsData: { store, @@ -28,22 +35,23 @@ const createComponent = (hideProjects = false) => { }); }; -const returnServicePromise = (data, failed) => new Promise((resolve, reject) => { - if (failed) { - reject(data); - } else { - resolve({ - json() { - return data; - }, - }); - } -}); +const returnServicePromise = (data, failed) => + new Promise((resolve, reject) => { + if (failed) { + reject(data); + } else { + resolve({ + json() { + return data; + }, + }); + } + }); describe('AppComponent', () => { let vm; - beforeEach((done) => { + beforeEach(done => { Vue.component('group-folder', groupFolderComponent); Vue.component('group-item', groupItemComponent); @@ -94,7 +102,7 @@ describe('AppComponent', () => { }); describe('fetchGroups', () => { - it('should call `getGroups` with all the params provided', (done) => { + it('should call `getGroups` with all the params provided', done => { spyOn(vm.service, 'getGroups').and.returnValue(returnServicePromise(mockGroups)); vm.fetchGroups({ @@ -110,8 +118,10 @@ describe('AppComponent', () => { }, 0); }); - it('should set headers to store for building pagination info when called with `updatePagination`', (done) => { - spyOn(vm.service, 'getGroups').and.returnValue(returnServicePromise({ headers: mockRawPageInfo })); + it('should set headers to store for building pagination info when called with `updatePagination`', done => { + spyOn(vm.service, 'getGroups').and.returnValue( + returnServicePromise({ headers: mockRawPageInfo }), + ); spyOn(vm, 'updatePagination'); vm.fetchGroups({ updatePagination: true }); @@ -122,7 +132,7 @@ describe('AppComponent', () => { }, 0); }); - it('should show flash error when request fails', (done) => { + it('should show flash error when request fails', done => { spyOn(vm.service, 'getGroups').and.returnValue(returnServicePromise(null, true)); spyOn($, 'scrollTo'); spyOn(window, 'Flash'); @@ -138,7 +148,7 @@ describe('AppComponent', () => { }); describe('fetchAllGroups', () => { - it('should fetch default set of groups', (done) => { + it('should fetch default set of groups', done => { spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockGroups)); spyOn(vm, 'updatePagination').and.callThrough(); spyOn(vm, 'updateGroups').and.callThrough(); @@ -153,7 +163,7 @@ describe('AppComponent', () => { }, 0); }); - it('should fetch matching set of groups when app is loaded with search query', (done) => { + it('should fetch matching set of groups when app is loaded with search query', done => { spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockSearchedGroups)); spyOn(vm, 'updateGroups').and.callThrough(); @@ -173,7 +183,7 @@ describe('AppComponent', () => { }); describe('fetchPage', () => { - it('should fetch groups for provided page details and update window state', (done) => { + it('should fetch groups for provided page details and update window state', done => { spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockGroups)); spyOn(vm, 'updateGroups').and.callThrough(); const mergeUrlParams = spyOnDependency(appComponent, 'mergeUrlParams').and.callThrough(); @@ -193,9 +203,13 @@ describe('AppComponent', () => { expect(vm.isLoading).toBe(false); expect($.scrollTo).toHaveBeenCalledWith(0); expect(mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String)); - expect(window.history.replaceState).toHaveBeenCalledWith({ - page: jasmine.any(String), - }, jasmine.any(String), jasmine.any(String)); + expect(window.history.replaceState).toHaveBeenCalledWith( + { + page: jasmine.any(String), + }, + jasmine.any(String), + jasmine.any(String), + ); expect(vm.updateGroups).toHaveBeenCalled(); done(); }, 0); @@ -211,7 +225,7 @@ describe('AppComponent', () => { groupItem.isChildrenLoading = false; }); - it('should fetch children of given group and expand it if group is collapsed and children are not loaded', (done) => { + it('should fetch children of given group and expand it if group is collapsed and children are not loaded', done => { spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockRawChildren)); spyOn(vm.store, 'setGroupChildren'); @@ -244,7 +258,7 @@ describe('AppComponent', () => { expect(groupItem.isOpen).toBe(false); }); - it('should set `isChildrenLoading` back to `false` if load request fails', (done) => { + it('should set `isChildrenLoading` back to `false` if load request fails', done => { spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise({}, true)); vm.toggleChildren(groupItem); @@ -272,7 +286,9 @@ describe('AppComponent', () => { expect(vm.groupLeaveConfirmationMessage).toBe(''); vm.showLeaveGroupModal(group, mockParentGroupItem); expect(vm.showModal).toBe(true); - expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`); + expect(vm.groupLeaveConfirmationMessage).toBe( + `Are you sure you want to leave the "${group.fullName}" group?`, + ); }); }); @@ -299,7 +315,7 @@ describe('AppComponent', () => { vm.targetParentGroup = groupItem; }); - it('hides modal confirmation leave group and remove group item from tree', (done) => { + it('hides modal confirmation leave group and remove group item from tree', done => { const notice = `You left the "${childGroupItem.fullName}" group.`; spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ notice })); spyOn(vm.store, 'removeGroup').and.callThrough(); @@ -318,9 +334,11 @@ describe('AppComponent', () => { }, 0); }); - it('should show error flash message if request failed to leave group', (done) => { + it('should show error flash message if request failed to leave group', done => { const message = 'An error occurred. Please try again.'; - spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ status: 500 }, true)); + spyOn(vm.service, 'leaveGroup').and.returnValue( + returnServicePromise({ status: 500 }, true), + ); spyOn(vm.store, 'removeGroup').and.callThrough(); spyOn(window, 'Flash'); @@ -335,9 +353,11 @@ describe('AppComponent', () => { }, 0); }); - it('should show appropriate error flash message if request forbids to leave group', (done) => { + it('should show appropriate error flash message if request forbids to leave group', done => { const message = 'Failed to leave the group. Please make sure you are not the only owner.'; - spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ status: 403 }, true)); + spyOn(vm.service, 'leaveGroup').and.returnValue( + returnServicePromise({ status: 403 }, true), + ); spyOn(vm.store, 'removeGroup').and.callThrough(); spyOn(window, 'Flash'); @@ -388,7 +408,7 @@ describe('AppComponent', () => { }); describe('created', () => { - it('should bind event listeners on eventHub', (done) => { + it('should bind event listeners on eventHub', done => { spyOn(eventHub, '$on'); const newVm = createComponent(); @@ -405,21 +425,21 @@ describe('AppComponent', () => { }); }); - it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `false`', (done) => { + it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `false`', done => { const newVm = createComponent(); newVm.$mount(); Vue.nextTick(() => { - expect(newVm.searchEmptyMessage).toBe('Sorry, no groups or projects matched your search'); + expect(newVm.searchEmptyMessage).toBe('No groups or projects matched your search'); newVm.$destroy(); done(); }); }); - it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `true`', (done) => { + it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `true`', done => { const newVm = createComponent(true); newVm.$mount(); Vue.nextTick(() => { - expect(newVm.searchEmptyMessage).toBe('Sorry, no groups matched your search'); + expect(newVm.searchEmptyMessage).toBe('No groups matched your search'); newVm.$destroy(); done(); }); @@ -427,7 +447,7 @@ describe('AppComponent', () => { }); describe('beforeDestroy', () => { - it('should unbind event listeners on eventHub', (done) => { + it('should unbind event listeners on eventHub', done => { spyOn(eventHub, '$off'); const newVm = createComponent(); @@ -454,7 +474,7 @@ describe('AppComponent', () => { vm.$destroy(); }); - it('should render loading icon', (done) => { + it('should render loading icon', done => { vm.isLoading = true; Vue.nextTick(() => { expect(vm.$el.querySelector('.loading-animation')).toBeDefined(); @@ -463,17 +483,16 @@ describe('AppComponent', () => { }); }); - it('should render groups tree', (done) => { + it('should render groups tree', done => { vm.store.state.groups = [mockParentGroupItem]; vm.isLoading = false; - vm.store.state.pageInfo = mockPageInfo; Vue.nextTick(() => { expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined(); done(); }); }); - it('renders modal confirmation dialog', (done) => { + it('renders modal confirmation dialog', done => { vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?'; vm.showModal = true; Vue.nextTick(() => { diff --git a/spec/javascripts/helpers/vue_resource_helper.js b/spec/javascripts/helpers/vue_resource_helper.js index 70b7ec4e574..0d1bf5e2e80 100644 --- a/spec/javascripts/helpers/vue_resource_helper.js +++ b/spec/javascripts/helpers/vue_resource_helper.js @@ -5,6 +5,7 @@ export const headersInterceptor = (request, next) => { response.headers.forEach((value, key) => { headers[key] = value; }); + // eslint-disable-next-line no-param-reassign response.headers = headers; }); }; diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js index 41d8bfff7e7..b45ae5bbb0f 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js @@ -30,7 +30,7 @@ describe('Multi-file editor commit sidebar list item', () => { }); it('renders file path', () => { - expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path); + expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent).toContain(f.path); }); it('renders actionn button', () => { diff --git a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js index a5b906da8a1..e09ccbe2a63 100644 --- a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js @@ -29,7 +29,7 @@ describe('IDE stage file button', () => { }); it('renders button to discard & stage', () => { - expect(vm.$el.querySelectorAll('.btn').length).toBe(2); + expect(vm.$el.querySelectorAll('.btn-blank').length).toBe(2); }); it('calls store with stage button', () => { @@ -39,7 +39,7 @@ describe('IDE stage file button', () => { }); it('calls store with discard button', () => { - vm.$el.querySelector('.dropdown-menu button').click(); + vm.$el.querySelector('.btn-danger').click(); expect(vm.discardFileChanges).toHaveBeenCalledWith(f.path); }); diff --git a/spec/javascripts/ide/components/file_templates/bar_spec.js b/spec/javascripts/ide/components/file_templates/bar_spec.js new file mode 100644 index 00000000000..a688f7f69a6 --- /dev/null +++ b/spec/javascripts/ide/components/file_templates/bar_spec.js @@ -0,0 +1,117 @@ +import Vue from 'vue'; +import { createStore } from '~/ide/stores'; +import Bar from '~/ide/components/file_templates/bar.vue'; +import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import { resetStore, file } from '../../helpers'; + +describe('IDE file templates bar component', () => { + let Component; + let vm; + + beforeAll(() => { + Component = Vue.extend(Bar); + }); + + beforeEach(() => { + const store = createStore(); + + store.state.openFiles.push({ + ...file('file'), + opened: true, + active: true, + }); + + vm = mountComponentWithStore(Component, { store }); + }); + + afterEach(() => { + vm.$destroy(); + resetStore(vm.$store); + }); + + describe('template type dropdown', () => { + it('renders dropdown component', () => { + expect(vm.$el.querySelector('.dropdown').textContent).toContain('Choose a type'); + }); + + it('calls setSelectedTemplateType when clicking item', () => { + spyOn(vm, 'setSelectedTemplateType').and.stub(); + + vm.$el.querySelector('.dropdown-content button').click(); + + expect(vm.setSelectedTemplateType).toHaveBeenCalledWith({ + name: '.gitlab-ci.yml', + key: 'gitlab_ci_ymls', + }); + }); + }); + + describe('template dropdown', () => { + beforeEach(done => { + vm.$store.state.fileTemplates.templates = [ + { + name: 'test', + }, + ]; + vm.$store.state.fileTemplates.selectedTemplateType = { + name: '.gitlab-ci.yml', + key: 'gitlab_ci_ymls', + }; + + vm.$nextTick(done); + }); + + it('renders dropdown component', () => { + expect(vm.$el.querySelectorAll('.dropdown')[1].textContent).toContain('Choose a template'); + }); + + it('calls fetchTemplate on click', () => { + spyOn(vm, 'fetchTemplate').and.stub(); + + vm.$el + .querySelectorAll('.dropdown-content')[1] + .querySelector('button') + .click(); + + expect(vm.fetchTemplate).toHaveBeenCalledWith({ + name: 'test', + }); + }); + }); + + it('shows undo button if updateSuccess is true', done => { + vm.$store.state.fileTemplates.updateSuccess = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.btn-default').style.display).not.toBe('none'); + + done(); + }); + }); + + it('calls undoFileTemplate when clicking undo button', () => { + spyOn(vm, 'undoFileTemplate').and.stub(); + + vm.$el.querySelector('.btn-default').click(); + + expect(vm.undoFileTemplate).toHaveBeenCalled(); + }); + + it('calls setSelectedTemplateType if activeFile name matches a template', done => { + const fileName = '.gitlab-ci.yml'; + + spyOn(vm, 'setSelectedTemplateType'); + vm.$store.state.openFiles[0].name = fileName; + + vm.setInitialType(); + + vm.$nextTick(() => { + expect(vm.setSelectedTemplateType).toHaveBeenCalledWith({ + name: fileName, + key: 'gitlab_ci_ymls', + }); + + done(); + }); + }); +}); diff --git a/spec/javascripts/ide/components/file_templates/dropdown_spec.js b/spec/javascripts/ide/components/file_templates/dropdown_spec.js new file mode 100644 index 00000000000..898796f4fa0 --- /dev/null +++ b/spec/javascripts/ide/components/file_templates/dropdown_spec.js @@ -0,0 +1,201 @@ +import $ from 'jquery'; +import Vue from 'vue'; +import { createStore } from '~/ide/stores'; +import Dropdown from '~/ide/components/file_templates/dropdown.vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import { resetStore } from '../../helpers'; + +describe('IDE file templates dropdown component', () => { + let Component; + let vm; + + beforeAll(() => { + Component = Vue.extend(Dropdown); + }); + + beforeEach(() => { + const store = createStore(); + + vm = createComponentWithStore(Component, store, { + label: 'Test', + }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + resetStore(vm.$store); + }); + + describe('async', () => { + beforeEach(() => { + vm.isAsyncData = true; + }); + + it('calls async store method on Bootstrap dropdown event', () => { + spyOn(vm, 'fetchTemplateTypes').and.stub(); + + $(vm.$el).trigger('show.bs.dropdown'); + + expect(vm.fetchTemplateTypes).toHaveBeenCalled(); + }); + + it('renders templates when async', done => { + vm.$store.state.fileTemplates.templates = [ + { + name: 'test', + }, + ]; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test'); + + done(); + }); + }); + + it('renders loading icon when isLoading is true', done => { + vm.$store.state.fileTemplates.isLoading = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.loading-container')).not.toBe(null); + + done(); + }); + }); + + it('searches template data', () => { + vm.$store.state.fileTemplates.templates = [ + { + name: 'test', + }, + ]; + vm.searchable = true; + vm.search = 'hello'; + + expect(vm.outputData).toEqual([]); + }); + + it('does not filter data is searchable is false', () => { + vm.$store.state.fileTemplates.templates = [ + { + name: 'test', + }, + ]; + vm.search = 'hello'; + + expect(vm.outputData).toEqual([ + { + name: 'test', + }, + ]); + }); + + it('calls clickItem on click', done => { + spyOn(vm, 'clickItem').and.stub(); + + vm.$store.state.fileTemplates.templates = [ + { + name: 'test', + }, + ]; + + vm.$nextTick(() => { + vm.$el.querySelector('.dropdown-content button').click(); + + expect(vm.clickItem).toHaveBeenCalledWith({ + name: 'test', + }); + + done(); + }); + }); + + it('renders input when searchable is true', done => { + vm.searchable = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null); + + done(); + }); + }); + + it('does not render input when searchable is true & showLoading is true', done => { + vm.searchable = true; + vm.$store.state.fileTemplates.isLoading = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.dropdown-input')).toBe(null); + + done(); + }); + }); + }); + + describe('sync', () => { + beforeEach(done => { + vm.data = [ + { + name: 'test sync', + }, + ]; + + vm.$nextTick(done); + }); + + it('renders props data', () => { + expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test sync'); + }); + + it('renders input when searchable is true', done => { + vm.searchable = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null); + + done(); + }); + }); + + it('calls clickItem on click', done => { + spyOn(vm, 'clickItem').and.stub(); + + vm.$nextTick(() => { + vm.$el.querySelector('.dropdown-content button').click(); + + expect(vm.clickItem).toHaveBeenCalledWith({ + name: 'test sync', + }); + + done(); + }); + }); + + it('searches template data', () => { + vm.searchable = true; + vm.search = 'hello'; + + expect(vm.outputData).toEqual([]); + }); + + it('does not filter data is searchable is false', () => { + vm.search = 'hello'; + + expect(vm.outputData).toEqual([ + { + name: 'test sync', + }, + ]); + }); + + it('renders dropdown title', done => { + vm.title = 'Test title'; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.dropdown-title').textContent).toContain('Test title'); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/components/repo_commit_section_spec.js b/spec/javascripts/ide/components/repo_commit_section_spec.js index 30cd92b2ca4..d09ccd7ac34 100644 --- a/spec/javascripts/ide/components/repo_commit_section_spec.js +++ b/spec/javascripts/ide/components/repo_commit_section_spec.js @@ -111,7 +111,7 @@ describe('RepoCommitSection', () => { .then(vm.$nextTick) .then(() => { expect(vm.$el.querySelector('.ide-commit-list-container').textContent).toContain( - 'No changes', + 'There are no unstaged changes', ); }) .then(done) @@ -133,7 +133,7 @@ describe('RepoCommitSection', () => { }); it('discards a single file', done => { - vm.$el.querySelector('.multi-file-discard-btn .dropdown-menu button').click(); + vm.$el.querySelector('.multi-file-commit-list li:first-child .js-modal-primary-action').click(); Vue.nextTick(() => { expect(vm.$el.querySelector('.ide-commit-list-container').textContent).not.toContain('file1'); diff --git a/spec/javascripts/ide/helpers.js b/spec/javascripts/ide/helpers.js index c11c482fef8..3ce9c9fcda1 100644 --- a/spec/javascripts/ide/helpers.js +++ b/spec/javascripts/ide/helpers.js @@ -5,6 +5,7 @@ import commitState from '~/ide/stores/modules/commit/state'; import mergeRequestsState from '~/ide/stores/modules/merge_requests/state'; import pipelinesState from '~/ide/stores/modules/pipelines/state'; import branchesState from '~/ide/stores/modules/branches/state'; +import fileTemplatesState from '~/ide/stores/modules/file_templates/state'; export const resetStore = store => { const newState = { @@ -13,6 +14,7 @@ export const resetStore = store => { mergeRequests: mergeRequestsState(), pipelines: pipelinesState(), branches: branchesState(), + fileTemplates: fileTemplatesState(), }; store.replaceState(newState); }; diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index bca2033ff97..1ca811e996b 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -692,21 +692,6 @@ describe('IDE store file actions', () => { .then(done) .catch(done.fail); }); - - it('calls scrollToTab', done => { - const scrollToTabSpy = jasmine.createSpy('scrollToTab'); - const oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line - store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line - - store - .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }) - .then(() => { - expect(scrollToTabSpy).toHaveBeenCalled(); - store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line - }) - .then(done) - .catch(done.fail); - }); }); describe('removePendingTab', () => { diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index d84f1717a61..c9a1158a14e 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -305,7 +305,11 @@ describe('Multi-file store actions', () => { describe('stageAllChanges', () => { it('adds all files from changedFiles to stagedFiles', done => { - store.state.changedFiles.push(file(), file('new')); + const openFile = { ...file(), path: 'test' }; + + store.state.openFiles.push(openFile); + store.state.stagedFiles.push(openFile); + store.state.changedFiles.push(openFile, file('new')); testAction( stageAllChanges, @@ -316,7 +320,12 @@ describe('Multi-file store actions', () => { { type: types.STAGE_CHANGE, payload: store.state.changedFiles[0].path }, { type: types.STAGE_CHANGE, payload: store.state.changedFiles[1].path }, ], - [], + [ + { + type: 'openPendingTab', + payload: { file: openFile, keyPrefix: 'staged' }, + }, + ], done, ); }); @@ -324,7 +333,11 @@ describe('Multi-file store actions', () => { describe('unstageAllChanges', () => { it('removes all files from stagedFiles after unstaging', done => { - store.state.stagedFiles.push(file(), file('new')); + const openFile = { ...file(), path: 'test' }; + + store.state.openFiles.push(openFile); + store.state.changedFiles.push(openFile); + store.state.stagedFiles.push(openFile, file('new')); testAction( unstageAllChanges, @@ -334,7 +347,12 @@ describe('Multi-file store actions', () => { { type: types.UNSTAGE_CHANGE, payload: store.state.stagedFiles[0].path }, { type: types.UNSTAGE_CHANGE, payload: store.state.stagedFiles[1].path }, ], - [], + [ + { + type: 'openPendingTab', + payload: { file: openFile, keyPrefix: 'unstaged' }, + }, + ], done, ); }); diff --git a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js index f831a9f0a5d..c29dd9f0d06 100644 --- a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js @@ -148,14 +148,66 @@ describe('IDE file templates actions', () => { }); describe('setSelectedTemplateType', () => { - it('commits SET_SELECTED_TEMPLATE_TYPE', done => { - testAction( - actions.setSelectedTemplateType, - 'test', - state, - [{ type: types.SET_SELECTED_TEMPLATE_TYPE, payload: 'test' }], - [], - done, + it('commits SET_SELECTED_TEMPLATE_TYPE', () => { + const commit = jasmine.createSpy('commit'); + const options = { + commit, + dispatch() {}, + rootGetters: { + activeFile: { + name: 'test', + prevPath: '', + }, + }, + }; + + actions.setSelectedTemplateType(options, { name: 'test' }); + + expect(commit).toHaveBeenCalledWith(types.SET_SELECTED_TEMPLATE_TYPE, { name: 'test' }); + }); + + it('dispatches discardFileChanges if prevPath matches templates name', () => { + const dispatch = jasmine.createSpy('dispatch'); + const options = { + commit() {}, + dispatch, + rootGetters: { + activeFile: { + name: 'test', + path: 'test', + prevPath: 'test', + }, + }, + }; + + actions.setSelectedTemplateType(options, { name: 'test' }); + + expect(dispatch).toHaveBeenCalledWith('discardFileChanges', 'test', { root: true }); + }); + + it('dispatches renameEntry if file name doesnt match', () => { + const dispatch = jasmine.createSpy('dispatch'); + const options = { + commit() {}, + dispatch, + rootGetters: { + activeFile: { + name: 'oldtest', + path: 'oldtest', + prevPath: '', + }, + }, + }; + + actions.setSelectedTemplateType(options, { name: 'test' }); + + expect(dispatch).toHaveBeenCalledWith( + 'renameEntry', + { + path: 'oldtest', + name: 'test', + }, + { root: true }, ); }); }); @@ -332,5 +384,20 @@ describe('IDE file templates actions', () => { expect(commit).toHaveBeenCalledWith('SET_UPDATE_SUCCESS', false); }); + + it('dispatches discardFileChanges if file has prevPath', () => { + const dispatch = jasmine.createSpy('dispatch'); + const rootGetters = { + activeFile: { path: 'test', prevPath: 'newtest', raw: 'raw content' }, + }; + + actions.undoFileTemplate({ dispatch, commit() {}, rootGetters }); + + expect(dispatch.calls.mostRecent().args).toEqual([ + 'discardFileChanges', + 'test', + { root: true }, + ]); + }); }); }); diff --git a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js b/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js index e337c3f331b..17cb457881f 100644 --- a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js +++ b/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js @@ -1,3 +1,5 @@ +import createState from '~/ide/stores/state'; +import { activityBarViews } from '~/ide/constants'; import * as getters from '~/ide/stores/modules/file_templates/getters'; describe('IDE file templates getters', () => { @@ -8,22 +10,49 @@ describe('IDE file templates getters', () => { }); describe('showFileTemplatesBar', () => { - it('finds template type by name', () => { + let rootState; + + beforeEach(() => { + rootState = createState(); + }); + + it('returns true if template is found and currentActivityView is edit', () => { + rootState.currentActivityView = activityBarViews.edit; + + expect( + getters.showFileTemplatesBar( + null, + { + templateTypes: getters.templateTypes(), + }, + rootState, + )('LICENSE'), + ).toBe(true); + }); + + it('returns false if template is found and currentActivityView is not edit', () => { + rootState.currentActivityView = activityBarViews.commit; + expect( - getters.showFileTemplatesBar(null, { - templateTypes: getters.templateTypes(), - })('LICENSE'), - ).toEqual({ - name: 'LICENSE', - key: 'licenses', - }); + getters.showFileTemplatesBar( + null, + { + templateTypes: getters.templateTypes(), + }, + rootState, + )('LICENSE'), + ).toBe(false); }); it('returns undefined if not found', () => { expect( - getters.showFileTemplatesBar(null, { - templateTypes: getters.templateTypes(), - })('test'), + getters.showFileTemplatesBar( + null, + { + templateTypes: getters.templateTypes(), + }, + rootState, + )('test'), ).toBe(undefined); }); }); diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js index 6ce76aaa03b..41dd3d3c67f 100644 --- a/spec/javascripts/ide/stores/mutations_spec.js +++ b/spec/javascripts/ide/stores/mutations_spec.js @@ -339,5 +339,13 @@ describe('Multi-file store mutations', () => { expect(localState.entries.parentPath.tree.length).toBe(1); }); + + it('adds to openFiles if previously opened', () => { + localState.entries.oldPath.opened = true; + + mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); + + expect(localState.openFiles).toEqual([localState.entries.newPath]); + }); }); }); diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js index 1d81e4e2d1a..c177d79b9e0 100644 --- a/spec/javascripts/lazy_loader_spec.js +++ b/spec/javascripts/lazy_loader_spec.js @@ -2,10 +2,10 @@ import LazyLoader from '~/lazy_loader'; let lazyLoader = null; -describe('LazyLoader', function () { +describe('LazyLoader', function() { preloadFixtures('issues/issue_with_comment.html.raw'); - beforeEach(function () { + beforeEach(function() { loadFixtures('issues/issue_with_comment.html.raw'); lazyLoader = new LazyLoader({ observerNode: 'body', @@ -13,8 +13,8 @@ describe('LazyLoader', function () { // Doing everything that happens normally in onload lazyLoader.loadCheck(); }); - describe('behavior', function () { - it('should copy value from data-src to src for img 1', function (done) { + describe('behavior', function() { + it('should copy value from data-src to src for img 1', function(done) { const img = document.querySelectorAll('img[data-src]')[0]; const originalDataSrc = img.getAttribute('data-src'); img.scrollIntoView(); @@ -26,7 +26,7 @@ describe('LazyLoader', function () { }, 100); }); - it('should lazy load dynamically added data-src images', function (done) { + it('should lazy load dynamically added data-src images', function(done) { const newImg = document.createElement('img'); const testPath = '/img/testimg.png'; newImg.className = 'lazy'; @@ -41,7 +41,7 @@ describe('LazyLoader', function () { }, 100); }); - it('should not alter normal images', function (done) { + it('should not alter normal images', function(done) { const newImg = document.createElement('img'); const testPath = '/img/testimg.png'; newImg.setAttribute('src', testPath); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index babad296f09..28151c7e658 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -29,24 +29,46 @@ describe('common_utils', () => { }); }); - describe('getUrlParamsArray', () => { - it('should return params array', () => { - expect(commonUtils.getUrlParamsArray() instanceof Array).toBe(true); + describe('urlParamsToArray', () => { + it('returns empty array for empty querystring', () => { + expect(commonUtils.urlParamsToArray('')).toEqual([]); + }); + + it('should decode params', () => { + expect( + commonUtils.urlParamsToArray('?label_name%5B%5D=test')[0], + ).toBe('label_name[]=test'); }); it('should remove the question mark from the search params', () => { - const paramsArray = commonUtils.getUrlParamsArray(); + const paramsArray = commonUtils.urlParamsToArray('?test=thing'); expect(paramsArray[0][0] !== '?').toBe(true); }); + }); - it('should decode params', () => { - window.history.pushState('', '', '?label_name%5B%5D=test'); + describe('urlParamsToObject', () => { + it('parses path for label with trailing +', () => { + expect( + commonUtils.urlParamsToObject('label_name[]=label%2B', {}), + ).toEqual({ + label_name: ['label+'], + }); + }); + it('parses path for milestone with trailing +', () => { expect( - commonUtils.getUrlParamsArray()[0], - ).toBe('label_name[]=test'); + commonUtils.urlParamsToObject('milestone_title=A%2B', {}), + ).toEqual({ + milestone_title: 'A+', + }); + }); - window.history.pushState('', '', '?'); + it('parses path for search terms with spaces', () => { + expect( + commonUtils.urlParamsToObject('search=two+words', {}), + ).toEqual({ + search: 'two words', + }); }); }); diff --git a/spec/javascripts/lib/utils/mock_data.js b/spec/javascripts/lib/utils/mock_data.js index fd0d62b751f..93d0d6259b9 100644 --- a/spec/javascripts/lib/utils/mock_data.js +++ b/spec/javascripts/lib/utils/mock_data.js @@ -2,4 +2,4 @@ export const faviconDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACA export const overlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA85JREFUWAntVllIVGEUPv/9b46O41KplYN7PeRkti8TjQlhCUGh3MmeQugpIsGKAi2soIcIooiohxYKK2daqDAlIpIiWwxtQaJcaHE0d5tMrbn37z9XRqfR0TvVW56Hudf//uec72zfEWBCJjIwkYGJDPzvGSD/KgExN3Oi2Q+2DJgSDYQEMwItVGH1iZGmJw/Si1y+/PwVAMYYib22MYc/8hVQFgKDEfYoId0KYzagAQebsos/ewMZoeB9wdffcTYpQSaCTWHKoqSQaDk7zkIt0+aCUR8BelEHrf3dUNv9AcqbnsHtT5UKB/hTASh0SLYjnjb/CIDRJi0XiFAaJOpCD8zLpdb4NB66b1OfelthX815dtdRRfiti2aAXLvVLiMQ6olGyztGDkSo4JGGXk8/QFdGpYzpHG2GBQTDhtgVhPEaVbbVpvI6GJz22rv4TcAfrYI1x7Rj5MWWAppomKFVVb2302SFzUkZHAbkG+0b1+Gh77yNYjrmqnWTrLBLRxdvBWv8qlFujH/kYjJYyvLkj71t78zAUvzMAMnHhpN4zf9UREJhd8omyssxu1IgazQDwDnHUcNuH6vhPIE1fmuBzHt74Hn7W89jWGtcAjoaIDOFrdcMYJBkgOCoaRF0Lj0oglddDbCj6tRvKjphEpgjkzEQs2YAKsNxMzjn3nKurhzK+Ly7xe28ua8TwgMMcHJZnvvT0BPtEEKM4tDJ+C8GvIIk4ylINIXVZ0EUKJxYuh3mhCeokbudl6TtVc88dfBdLwbyaWB6zQCYQJpBYSrDGQxBQ/ZWRM2B+VNmQnVnHWx7elyNuL2/R336co7KyJR8CL9oLgEuFlREevWUkEl6uGwpVEG4FBm0OEf9N10NMgPlvWYAuNVwsWDKvcUNYsHUWTCZ13ysyFEXe6TO6aC8CUr9IiK+A05TQrc8yjwmxARHeeMAPlfQJw+AQRwu0YhL/GDXi9NwufG+S8dYkuYMqIb4SsWthotlNMOUCOM6r+G9cqXxPmd1dqrBav/o1zJy2l5/NUjJA/VORwYuFnOUaTQcPs9wMqwV++Xv8oADxKAcZ8nLPr8AoGW+xR6HSqYk3GodAz2QNj0V+Gr26dT9ASNH5239Pf0gktVNWZca8ZvfAFBprWS6hSu1pqt++Y0PD+WIwDAhIWQGtzvSHDbcodfFUFB9hg1Gjs5LXqIdFL+acFBl+FddqYwdxsWC3I70OvgfUaA65zhq2O2c8VxYcyIGFTVlXegYtvCXANCQZJMobjVcLMjtSK/IcEgyOOe8Ve5w7ryKDefp2P3+C/5ohv8HZmVLAAAAAElFTkSuQmCC'; -export const faviconWithOverlayDataUrl = 'data:image/png;base64,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'; +export const faviconWithOverlayDataUrl = 'data:image/png;base64,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'; diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js index d60485b1308..ac3270baef5 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ b/spec/javascripts/lib/utils/text_utility_spec.js @@ -63,6 +63,12 @@ describe('text_utility', () => { }); }); + describe('slugifyWithHyphens', () => { + it('should replaces whitespaces with hyphens and convert to lower case', () => { + expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string'); + }); + }); + describe('stripHtml', () => { it('replaces html tag with the default replacement', () => { expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual( diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js index 19278312b6d..a837b71db0b 100644 --- a/spec/javascripts/monitoring/graph/flag_spec.js +++ b/spec/javascripts/monitoring/graph/flag_spec.js @@ -35,7 +35,7 @@ const defaultValuesComponent = { unitOfDisplay: 'ms', currentDataIndex: 0, legendTitle: 'Average', - currentCoordinates: [], + currentCoordinates: {}, }; const deploymentFlagData = { diff --git a/spec/javascripts/monitoring/graph_spec.js b/spec/javascripts/monitoring/graph_spec.js index a46a387a534..990619b4109 100644 --- a/spec/javascripts/monitoring/graph_spec.js +++ b/spec/javascripts/monitoring/graph_spec.js @@ -113,6 +113,9 @@ describe('Graph', () => { projectPath, }); + // simulate moving mouse over data series + component.seriesUnderMouse = component.timeSeries; + component.positionFlag(); expect(component.currentData).toBe(component.timeSeries[0].values[10]); }); diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js index 52cc42cb53d..d7298cb3483 100644 --- a/spec/javascripts/notes/components/note_actions_spec.js +++ b/spec/javascripts/notes/components/note_actions_spec.js @@ -28,7 +28,7 @@ describe('issue_note_actions component', () => { canEdit: true, canAwardEmoji: true, canReportAsAbuse: true, - noteId: 539, + noteId: '539', noteUrl: 'https://localhost:3000/group/project/merge_requests/1#note_1', reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26', @@ -59,6 +59,20 @@ describe('issue_note_actions component', () => { expect(vm.$el.querySelector(`a[href="${props.reportAbusePath}"]`)).toBeDefined(); }); + it('should be possible to copy link to a note', () => { + expect(vm.$el.querySelector('.js-btn-copy-note-link')).not.toBeNull(); + }); + + it('should not show copy link action when `noteUrl` prop is empty', done => { + vm.noteUrl = ''; + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-btn-copy-note-link')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + it('should be possible to delete comment', () => { expect(vm.$el.querySelector('.js-note-delete')).toBeDefined(); }); @@ -77,7 +91,7 @@ describe('issue_note_actions component', () => { canEdit: false, canAwardEmoji: false, canReportAsAbuse: false, - noteId: 539, + noteId: '539', noteUrl: 'https://localhost:3000/group/project/merge_requests/1#note_1', reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26', diff --git a/spec/javascripts/notes/components/note_awards_list_spec.js b/spec/javascripts/notes/components/note_awards_list_spec.js index 9d98ba219da..6a6a810acff 100644 --- a/spec/javascripts/notes/components/note_awards_list_spec.js +++ b/spec/javascripts/notes/components/note_awards_list_spec.js @@ -30,7 +30,7 @@ describe('note_awards_list component', () => { propsData: { awards: awardsMock, noteAuthorId: 2, - noteId: 545, + noteId: '545', canAwardEmoji: true, toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji', }, @@ -70,7 +70,7 @@ describe('note_awards_list component', () => { propsData: { awards: awardsMock, noteAuthorId: 2, - noteId: 545, + noteId: '545', canAwardEmoji: false, toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji', }, diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js index 95d400ab3df..147ffcf1b81 100644 --- a/spec/javascripts/notes/components/note_form_spec.js +++ b/spec/javascripts/notes/components/note_form_spec.js @@ -19,7 +19,7 @@ describe('issue_note_form component', () => { props = { isEditing: false, noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.', - noteId: 545, + noteId: '545', }; vm = new Component({ @@ -32,6 +32,22 @@ describe('issue_note_form component', () => { vm.$destroy(); }); + describe('noteHash', () => { + it('returns note hash string based on `noteId`', () => { + expect(vm.noteHash).toBe(`#note_${props.noteId}`); + }); + + it('return note hash as `#` when `noteId` is empty', done => { + vm.noteId = ''; + Vue.nextTick() + .then(() => { + expect(vm.noteHash).toBe('#'); + }) + .then(done) + .catch(done.fail); + }); + }); + describe('conflicts editing', () => { it('should show conflict message if note changes outside the component', done => { vm.isEditing = true; diff --git a/spec/javascripts/notes/components/note_header_spec.js b/spec/javascripts/notes/components/note_header_spec.js index a3c6bf78988..379780f43a0 100644 --- a/spec/javascripts/notes/components/note_header_spec.js +++ b/spec/javascripts/notes/components/note_header_spec.js @@ -33,7 +33,7 @@ describe('note_header component', () => { }, createdAt: '2017-08-02T10:51:58.559Z', includeToggle: false, - noteId: 1394, + noteId: '1394', expanded: true, }, }).$mount(); @@ -47,6 +47,16 @@ describe('note_header component', () => { it('should render timestamp link', () => { expect(vm.$el.querySelector('a[href="#note_1394"]')).toBeDefined(); }); + + it('should not render user information when prop `author` is empty object', done => { + vm.author = {}; + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.note-header-author-name')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); }); describe('discussion', () => { @@ -66,7 +76,7 @@ describe('note_header component', () => { }, createdAt: '2017-08-02T10:51:58.559Z', includeToggle: true, - noteId: 1395, + noteId: '1395', expanded: true, }, }).$mount(); diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 0423fcb6ec4..1f030e5af28 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -66,7 +66,7 @@ export const individualNote = { individual_note: true, notes: [ { - id: 1390, + id: '1390', attachment: { url: null, filename: null, @@ -111,7 +111,7 @@ export const individualNote = { }; export const note = { - id: 546, + id: '546', attachment: { url: null, filename: null, @@ -174,7 +174,7 @@ export const discussionMock = { expanded: true, notes: [ { - id: 1395, + id: '1395', attachment: { url: null, filename: null, @@ -211,7 +211,7 @@ export const discussionMock = { path: '/gitlab-org/gitlab-ce/notes/1395', }, { - id: 1396, + id: '1396', attachment: { url: null, filename: null, @@ -257,7 +257,7 @@ export const discussionMock = { path: '/gitlab-org/gitlab-ce/notes/1396', }, { - id: 1437, + id: '1437', attachment: { url: null, filename: null, @@ -308,7 +308,7 @@ export const discussionMock = { }; export const loggedOutnoteableData = { - id: 98, + id: '98', iid: 26, author_id: 1, description: '', @@ -358,7 +358,7 @@ export const collapseNotesMock = [ individual_note: true, notes: [ { - id: 1390, + id: '1390', attachment: null, author: { id: 1, @@ -393,7 +393,7 @@ export const collapseNotesMock = [ individual_note: true, notes: [ { - id: 1391, + id: '1391', attachment: null, author: { id: 1, @@ -433,7 +433,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = { expanded: true, notes: [ { - id: 1390, + id: '1390', attachment: { url: null, filename: null, @@ -495,7 +495,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = { expanded: true, notes: [ { - id: 1391, + id: '1391', attachment: { url: null, filename: null, @@ -544,7 +544,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = { '/gitlab-org/gitlab-ce/notes/1471': { commands_changes: null, valid: true, - id: 1471, + id: '1471', attachment: null, author: { id: 1, @@ -600,7 +600,7 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = { expanded: true, notes: [ { - id: 1471, + id: '1471', attachment: { url: null, filename: null, @@ -671,7 +671,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 901, + id: '901', type: null, attachment: null, author: { @@ -718,7 +718,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 902, + id: '902', type: null, attachment: null, author: { @@ -765,7 +765,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 903, + id: '903', type: null, attachment: null, author: { @@ -809,7 +809,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 904, + id: '904', type: null, attachment: null, author: { @@ -854,7 +854,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 905, + id: '905', type: null, attachment: null, author: { @@ -898,7 +898,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 906, + id: '906', type: null, attachment: null, author: { @@ -945,7 +945,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 901, + id: '901', type: null, attachment: null, author: { @@ -992,7 +992,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 902, + id: '902', type: null, attachment: null, author: { @@ -1039,7 +1039,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 904, + id: '904', type: null, attachment: null, author: { @@ -1084,7 +1084,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 905, + id: '905', type: null, attachment: null, author: { @@ -1129,7 +1129,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 906, + id: '906', type: null, attachment: null, author: { diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js index 84515d2bf97..dace834a3c8 100644 --- a/spec/javascripts/projects/project_new_spec.js +++ b/spec/javascripts/projects/project_new_spec.js @@ -4,12 +4,14 @@ import projectNew from '~/projects/project_new'; describe('New Project', () => { let $projectImportUrl; let $projectPath; + let $projectName; beforeEach(() => { setFixtures(` <div class='toggle-import-form'> <div class='import-url-data'> <input id="project_import_url" /> + <input id="project_name" /> <input id="project_path" /> </div> </div> @@ -17,6 +19,7 @@ describe('New Project', () => { $projectImportUrl = $('#project_import_url'); $projectPath = $('#project_path'); + $projectName = $('#project_name'); }); describe('deriveProjectPathFromUrl', () => { @@ -129,4 +132,31 @@ describe('New Project', () => { }); }); }); + + describe('deriveSlugFromProjectName', () => { + beforeEach(() => { + projectNew.bindEvents(); + $projectName.val('').keyup(); + }); + + it('converts project name to lower case and dash-limited slug', () => { + const dummyProjectName = 'My Awesome Project'; + + $projectName.val(dummyProjectName); + + projectNew.onProjectNameChange($projectName, $projectPath); + + expect($projectPath.val()).toEqual('my-awesome-project'); + }); + + it('does not add additional dashes in the slug if the project name already contains dashes', () => { + const dummyProjectName = 'My-Dash-Delimited Awesome Project'; + + $projectName.val(dummyProjectName); + + projectNew.onProjectNameChange($projectName, $projectPath); + + expect($projectPath.val()).toEqual('my-dash-delimited-awesome-project'); + }); + }); }); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 4452c470b82..b89d10cb993 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -1,6 +1,7 @@ /* eslint-disable jasmine/no-global-setup, jasmine/no-unsafe-spy, no-underscore-dangle, no-console */ +/* global __karma__ */ import $ from 'jquery'; import 'vendor/jasmine-jquery'; @@ -41,8 +42,8 @@ jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH; beforeAll(() => { jasmine.addMatchers( jasmineDiff(jasmine, { - colors: true, - inline: true, + colors: !__karma__.config.isCi, + inline: !__karma__.config.isCi, }), ); jasmine.addMatchers(customMatchers); diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js index 1c666fc6c55..f2a09d08829 100644 --- a/spec/javascripts/vue_shared/components/file_icon_spec.js +++ b/spec/javascripts/vue_shared/components/file_icon_spec.js @@ -62,9 +62,11 @@ describe('File Icon component', () => { loading: true, }); - expect( - vm.$el.querySelector('i').getAttribute('class'), - ).toEqual('fa fa-spin fa-spinner fa-1x'); + const { classList } = vm.$el.querySelector('i'); + expect(classList.contains('fa')).toEqual(true); + expect(classList.contains('fa-spin')).toEqual(true); + expect(classList.contains('fa-spinner')).toEqual(true); + expect(classList.contains('fa-1x')).toEqual(true); }); it('should add a special class and a size class', () => { diff --git a/spec/javascripts/vue_shared/components/loading_icon_spec.js b/spec/javascripts/vue_shared/components/loading_icon_spec.js deleted file mode 100644 index 5cd3466f501..00000000000 --- a/spec/javascripts/vue_shared/components/loading_icon_spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import Vue from 'vue'; -import loadingIcon from '~/vue_shared/components/loading_icon.vue'; - -describe('Loading Icon Component', () => { - let LoadingIconComponent; - - beforeEach(() => { - LoadingIconComponent = Vue.extend(loadingIcon); - }); - - it('should render a spinner font awesome icon', () => { - const component = new LoadingIconComponent().$mount(); - - expect( - component.$el.querySelector('i').getAttribute('class'), - ).toEqual('fa fa-spin fa-spinner fa-1x'); - - expect(component.$el.tagName).toEqual('DIV'); - expect(component.$el.classList).toContain('text-center'); - expect(component.$el.classList).toContain('loading-container'); - }); - - it('should render accessibility attributes', () => { - const component = new LoadingIconComponent().$mount(); - - const icon = component.$el.querySelector('i'); - expect(icon.getAttribute('aria-hidden')).toEqual('true'); - expect(icon.getAttribute('aria-label')).toEqual('Loading'); - }); - - it('should render the provided label', () => { - const component = new LoadingIconComponent({ - propsData: { - label: 'This is a loading icon', - }, - }).$mount(); - - expect( - component.$el.querySelector('i').getAttribute('aria-label'), - ).toEqual('This is a loading icon'); - }); - - it('should render the provided size', () => { - const component = new LoadingIconComponent({ - propsData: { - size: '2', - }, - }).$mount(); - - expect( - component.$el.querySelector('i').classList.contains('fa-2x'), - ).toEqual(true); - }); -}); diff --git a/spec/javascripts/vue_shared/components/notes/system_note_spec.js b/spec/javascripts/vue_shared/components/notes/system_note_spec.js index 2a6015fe35f..adcb1c858aa 100644 --- a/spec/javascripts/vue_shared/components/notes/system_note_spec.js +++ b/spec/javascripts/vue_shared/components/notes/system_note_spec.js @@ -9,7 +9,7 @@ describe('system note component', () => { beforeEach(() => { props = { note: { - id: 1424, + id: '1424', author: { id: 1, name: 'Root', diff --git a/spec/javascripts/vue_shared/components/pagination_links_spec.js b/spec/javascripts/vue_shared/components/pagination_links_spec.js new file mode 100644 index 00000000000..c9d183872b4 --- /dev/null +++ b/spec/javascripts/vue_shared/components/pagination_links_spec.js @@ -0,0 +1,72 @@ +import Vue from 'vue'; +import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; +import { s__ } from '~/locale'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Pagination links component', () => { + const paginationLinksComponent = Vue.extend(PaginationLinks); + const change = page => page; + const pageInfo = { + page: 3, + perPage: 5, + total: 30, + }; + const translations = { + firstText: s__('Pagination|« First'), + prevText: s__('Pagination|Prev'), + nextText: s__('Pagination|Next'), + lastText: s__('Pagination|Last »'), + }; + + let paginationLinks; + let glPagination; + let destinationComponent; + + beforeEach(() => { + paginationLinks = mountComponent( + paginationLinksComponent, + { + change, + pageInfo, + }, + ); + [glPagination] = paginationLinks.$children; + [destinationComponent] = glPagination.$children; + }); + + afterEach(() => { + paginationLinks.$destroy(); + }); + + it('should provide translated text to GitLab UI pagination', () => { + Object.entries(translations).forEach(entry => + expect( + destinationComponent[entry[0]], + ).toBe(entry[1]), + ); + }); + + it('should pass change to GitLab UI pagination', () => { + expect( + Object.is(glPagination.change, change), + ).toBe(true); + }); + + it('should pass page from pageInfo to GitLab UI pagination', () => { + expect( + destinationComponent.value, + ).toBe(pageInfo.page); + }); + + it('should pass per page from pageInfo to GitLab UI pagination', () => { + expect( + destinationComponent.perPage, + ).toBe(pageInfo.perPage); + }); + + it('should pass total items from pageInfo to GitLab UI pagination', () => { + expect( + destinationComponent.totalRows, + ).toBe(pageInfo.total); + }); +}); |