diff options
Diffstat (limited to 'spec/frontend/diffs')
-rw-r--r-- | spec/frontend/diffs/components/diff_file_spec.js | 2 | ||||
-rw-r--r-- | spec/frontend/diffs/components/diff_line_note_form_spec.js | 14 | ||||
-rw-r--r-- | spec/frontend/diffs/components/inline_diff_view_spec.js | 2 | ||||
-rw-r--r-- | spec/frontend/diffs/components/parallel_diff_view_spec.js | 2 | ||||
-rw-r--r-- | spec/frontend/diffs/mock_data/diff_file.js | 1 | ||||
-rw-r--r-- | spec/frontend/diffs/mock_data/diff_metadata.js | 58 | ||||
-rw-r--r-- | spec/frontend/diffs/store/actions_spec.js | 228 | ||||
-rw-r--r-- | spec/frontend/diffs/store/utils_spec.js | 243 | ||||
-rw-r--r-- | spec/frontend/diffs/utils/uuids_spec.js | 92 |
9 files changed, 454 insertions, 188 deletions
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js index d0ba71fce47..71e975f2409 100644 --- a/spec/frontend/diffs/components/diff_file_spec.js +++ b/spec/frontend/diffs/components/diff_file_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import { createStore } from 'ee_else_ce/mr_notes/stores'; +import { createStore } from '~/mr_notes/stores'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { mockTracking, triggerEvent } from 'helpers/tracking_helper'; import DiffFileComponent from '~/diffs/components/diff_file.vue'; diff --git a/spec/frontend/diffs/components/diff_line_note_form_spec.js b/spec/frontend/diffs/components/diff_line_note_form_spec.js index 3e0acd0dace..623df8bd55e 100644 --- a/spec/frontend/diffs/components/diff_line_note_form_spec.js +++ b/spec/frontend/diffs/components/diff_line_note_form_spec.js @@ -77,12 +77,24 @@ describe('DiffLineNoteForm', () => { .spyOn(wrapper.vm, 'saveDiffDiscussion') .mockReturnValue(Promise.resolve()); + const lineRange = { + start_line_code: wrapper.vm.commentLineStart.lineCode, + start_line_type: wrapper.vm.commentLineStart.type, + end_line_code: wrapper.vm.line.line_code, + end_line_type: wrapper.vm.line.type, + }; + + const formData = { + ...wrapper.vm.formData, + lineRange, + }; + wrapper.vm .handleSaveNote('note body') .then(() => { expect(saveDiffDiscussionSpy).toHaveBeenCalledWith({ note: 'note body', - formData: wrapper.vm.formData, + formData, }); }) .then(done) diff --git a/spec/frontend/diffs/components/inline_diff_view_spec.js b/spec/frontend/diffs/components/inline_diff_view_spec.js index 9b0cf6a84d9..eeef8e5a7b0 100644 --- a/spec/frontend/diffs/components/inline_diff_view_spec.js +++ b/spec/frontend/diffs/components/inline_diff_view_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import '~/behaviors/markdown/render_gfm'; -import { createStore } from 'ee_else_ce/mr_notes/stores'; +import { createStore } from '~/mr_notes/stores'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import InlineDiffView from '~/diffs/components/inline_diff_view.vue'; import diffFileMockData from '../mock_data/diff_file'; diff --git a/spec/frontend/diffs/components/parallel_diff_view_spec.js b/spec/frontend/diffs/components/parallel_diff_view_spec.js index 03cf1b72b62..30231f0ba71 100644 --- a/spec/frontend/diffs/components/parallel_diff_view_spec.js +++ b/spec/frontend/diffs/components/parallel_diff_view_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import { createStore } from 'ee_else_ce/mr_notes/stores'; +import { createStore } from '~/mr_notes/stores'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue'; import * as constants from '~/diffs/constants'; diff --git a/spec/frontend/diffs/mock_data/diff_file.js b/spec/frontend/diffs/mock_data/diff_file.js index 27428197c1c..e4b2fdf6ede 100644 --- a/spec/frontend/diffs/mock_data/diff_file.js +++ b/spec/frontend/diffs/mock_data/diff_file.js @@ -13,6 +13,7 @@ export default { blob_name: 'CHANGELOG', blob_icon: '<i aria-hidden="true" data-hidden="true" class="fa fa-file-text-o fa-fw"></i>', file_hash: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a', + file_identifier_hash: '928f8286952bda02d674b692addcbe077084663a', file_path: 'CHANGELOG', new_file: false, deleted_file: false, diff --git a/spec/frontend/diffs/mock_data/diff_metadata.js b/spec/frontend/diffs/mock_data/diff_metadata.js new file mode 100644 index 00000000000..b73b29e4bc8 --- /dev/null +++ b/spec/frontend/diffs/mock_data/diff_metadata.js @@ -0,0 +1,58 @@ +/* eslint-disable import/prefer-default-export */ +/* https://gitlab.com/gitlab-org/frontend/rfcs/-/issues/20 */ + +export const diffMetadata = { + real_size: '1', + size: 1, + branch_name: 'update-changelog', + source_branch_exists: true, + target_branch_name: 'master', + commit: null, + context_commits: null, + merge_request_diff: { + version_index: null, + created_at: '2019-11-07T06:48:35.202Z', + commits_count: 1, + latest: true, + short_commit_sha: 'eb227b3e', + base_version_path: '/gitlab-org/gitlab-test/-/merge_requests/4/diffs?diff_id=4', + head_version_path: '/gitlab-org/gitlab-test/-/merge_requests/4/diffs?diff_head=true', + version_path: '/gitlab-org/gitlab-test/-/merge_requests/4/diffs?diff_id=4', + compare_path: + '/gitlab-org/gitlab-test/-/merge_requests/4/diffs?diff_id=4\u0026start_sha=eb227b3e214624708c474bdab7bde7afc17cefcc', + }, + start_version: null, + latest_diff: true, + latest_version_path: '/gitlab-org/gitlab-test/-/merge_requests/4/diffs', + added_lines: 2, + removed_lines: 0, + render_overflow_warning: false, + email_patch_path: '/gitlab-org/gitlab-test/-/merge_requests/4.patch', + plain_diff_path: '/gitlab-org/gitlab-test/-/merge_requests/4.diff', + merge_request_diffs: [ + { + version_index: null, + created_at: '2019-11-07T06:48:35.202Z', + commits_count: 1, + latest: true, + short_commit_sha: 'eb227b3e', + base_version_path: '/gitlab-org/gitlab-test/-/merge_requests/4/diffs?diff_id=4', + head_version_path: '/gitlab-org/gitlab-test/-/merge_requests/4/diffs?diff_head=true', + version_path: '/gitlab-org/gitlab-test/-/merge_requests/4/diffs?diff_id=4', + compare_path: + '/gitlab-org/gitlab-test/-/merge_requests/4/diffs?diff_id=4\u0026start_sha=eb227b3e214624708c474bdab7bde7afc17cefcc', + }, + ], + diff_files: [ + { + added_lines: 2, + removed_lines: 0, + new_path: 'CHANGELOG', + old_path: 'CHANGELOG', + new_file: false, + deleted_file: false, + file_identifier_hash: '928f8286952bda02d674b692addcbe077084663a', + file_hash: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a', + }, + ], +}; diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index 3fba661da44..7d79dcfbfe3 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -35,8 +35,6 @@ import { setRenderTreeList, setShowWhitespace, setRenderIt, - requestFullDiff, - receiveFullDiffSucess, receiveFullDiffError, fetchFullDiff, toggleFullDiff, @@ -53,7 +51,9 @@ import axios from '~/lib/utils/axios_utils'; import testAction from '../../helpers/vuex_action_helper'; import * as utils from '~/diffs/store/utils'; import * as commonUtils from '~/lib/utils/common_utils'; +import { mergeUrlParams } from '~/lib/utils/url_utility'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; +import { diffMetadata } from '../mock_data/diff_metadata'; import createFlash from '~/flash'; jest.mock('~/flash', () => jest.fn()); @@ -175,19 +175,44 @@ describe('DiffsStoreActions', () => { }); describe('fetchDiffFilesBatch', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + it('should fetch batch diff files', done => { const endpointBatch = '/fetch/diffs_batch'; - const mock = new MockAdapter(axios); const res1 = { diff_files: [], pagination: { next_page: 2 } }; const res2 = { diff_files: [], pagination: {} }; mock - .onGet(endpointBatch, { - params: { page: 1, per_page: DIFFS_PER_PAGE, w: '1', view: 'inline' }, - }) + .onGet( + mergeUrlParams( + { + per_page: DIFFS_PER_PAGE, + w: '1', + view: 'inline', + page: 1, + }, + endpointBatch, + ), + ) .reply(200, res1) - .onGet(endpointBatch, { - params: { page: 2, per_page: DIFFS_PER_PAGE, w: '1', view: 'inline' }, - }) + .onGet( + mergeUrlParams( + { + per_page: DIFFS_PER_PAGE, + w: '1', + view: 'inline', + page: 2, + }, + endpointBatch, + ), + ) .reply(200, res2); testAction( @@ -204,22 +229,50 @@ describe('DiffsStoreActions', () => { { type: types.SET_RETRIEVING_BATCHES, payload: false }, ], [], - () => { - mock.restore(); - done(); - }, + done, ); }); + + it.each` + viewStyle | otherView + ${'inline'} | ${'parallel'} + ${'parallel'} | ${'inline'} + `( + 'should make a request with the view parameter "$viewStyle" when the batchEndpoint already contains "$otherView"', + ({ viewStyle, otherView }) => { + const endpointBatch = '/fetch/diffs_batch'; + + fetchDiffFilesBatch({ + commit: () => {}, + state: { + endpointBatch: `${endpointBatch}?view=${otherView}`, + useSingleDiffStyle: true, + diffViewType: viewStyle, + }, + }) + .then(() => { + expect(mock.history.get[0].url).toContain(`view=${viewStyle}`); + expect(mock.history.get[0].url).not.toContain(`view=${otherView}`); + }) + .catch(() => {}); + }, + ); }); describe('fetchDiffFilesMeta', () => { - it('should fetch diff meta information', done => { - const endpointMetadata = '/fetch/diffs_meta?view=inline'; - const mock = new MockAdapter(axios); - const data = { diff_files: [] }; - const res = { data }; - mock.onGet(endpointMetadata).reply(200, res); + const endpointMetadata = '/fetch/diffs_metadata.json?view=inline'; + const noFilesData = { ...diffMetadata }; + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + + delete noFilesData.diff_files; + + mock.onGet(endpointMetadata).reply(200, diffMetadata); + }); + it('should fetch diff meta information', done => { testAction( fetchDiffFilesMeta, {}, @@ -227,8 +280,8 @@ describe('DiffsStoreActions', () => { [ { type: types.SET_LOADING, payload: true }, { type: types.SET_LOADING, payload: false }, - { type: types.SET_MERGE_REQUEST_DIFFS, payload: [] }, - { type: types.SET_DIFF_DATA, payload: { data } }, + { type: types.SET_MERGE_REQUEST_DIFFS, payload: diffMetadata.merge_request_diffs }, + { type: types.SET_DIFF_DATA, payload: noFilesData }, ], [], () => { @@ -280,15 +333,24 @@ describe('DiffsStoreActions', () => { }); describe('fetchDiffFilesBatch', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + it('should fetch batch diff files', done => { const endpointBatch = '/fetch/diffs_batch'; - const mock = new MockAdapter(axios); const res1 = { diff_files: [], pagination: { next_page: 2 } }; const res2 = { diff_files: [], pagination: {} }; mock - .onGet(endpointBatch, { params: { page: 1, per_page: DIFFS_PER_PAGE, w: '1' } }) + .onGet(mergeUrlParams({ per_page: DIFFS_PER_PAGE, w: '1', page: 1 }, endpointBatch)) .reply(200, res1) - .onGet(endpointBatch, { params: { page: 2, per_page: DIFFS_PER_PAGE, w: '1' } }) + .onGet(mergeUrlParams({ per_page: DIFFS_PER_PAGE, w: '1', page: 2 }, endpointBatch)) .reply(200, res2); testAction( @@ -305,22 +367,48 @@ describe('DiffsStoreActions', () => { { type: types.SET_RETRIEVING_BATCHES, payload: false }, ], [], - () => { - mock.restore(); - done(); - }, + done, ); }); + + it.each` + querystrings | requestUrl + ${'?view=parallel'} | ${'/fetch/diffs_batch?view=parallel'} + ${'?view=inline'} | ${'/fetch/diffs_batch?view=inline'} + ${''} | ${'/fetch/diffs_batch'} + `( + 'should use the endpoint $requestUrl if the endpointBatch in state includes `$querystrings` as a querystring', + ({ querystrings, requestUrl }) => { + const endpointBatch = '/fetch/diffs_batch'; + + fetchDiffFilesBatch({ + commit: () => {}, + state: { + endpointBatch: `${endpointBatch}${querystrings}`, + diffViewType: 'inline', + }, + }) + .then(() => { + expect(mock.history.get[0].url).toEqual(requestUrl); + }) + .catch(() => {}); + }, + ); }); describe('fetchDiffFilesMeta', () => { - it('should fetch diff meta information', done => { - const endpointMetadata = '/fetch/diffs_meta'; - const mock = new MockAdapter(axios); - const data = { diff_files: [] }; - const res = { data }; - mock.onGet(endpointMetadata).reply(200, res); + const endpointMetadata = '/fetch/diffs_metadata.json'; + const noFilesData = { ...diffMetadata }; + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + + delete noFilesData.diff_files; + mock.onGet(endpointMetadata).reply(200, diffMetadata); + }); + it('should fetch diff meta information', done => { testAction( fetchDiffFilesMeta, {}, @@ -328,8 +416,8 @@ describe('DiffsStoreActions', () => { [ { type: types.SET_LOADING, payload: true }, { type: types.SET_LOADING, payload: false }, - { type: types.SET_MERGE_REQUEST_DIFFS, payload: [] }, - { type: types.SET_DIFF_DATA, payload: { data } }, + { type: types.SET_MERGE_REQUEST_DIFFS, payload: diffMetadata.merge_request_diffs }, + { type: types.SET_DIFF_DATA, payload: noFilesData }, ], [], () => { @@ -467,9 +555,9 @@ describe('DiffsStoreActions', () => { new_path: 'file1', old_line: 5, old_path: 'file2', + line_range: null, line_code: 'ABC_1_1', position_type: 'text', - line_range: null, }, }, hash: 'ABC_123', @@ -1136,34 +1224,8 @@ describe('DiffsStoreActions', () => { }); }); - describe('requestFullDiff', () => { - it('commits REQUEST_FULL_DIFF', done => { - testAction( - requestFullDiff, - 'file', - {}, - [{ type: types.REQUEST_FULL_DIFF, payload: 'file' }], - [], - done, - ); - }); - }); - - describe('receiveFullDiffSucess', () => { - it('commits REQUEST_FULL_DIFF', done => { - testAction( - receiveFullDiffSucess, - { filePath: 'test' }, - {}, - [{ type: types.RECEIVE_FULL_DIFF_SUCCESS, payload: { filePath: 'test' } }], - [], - done, - ); - }); - }); - describe('receiveFullDiffError', () => { - it('commits REQUEST_FULL_DIFF', done => { + it('updates state with the file that did not load', done => { testAction( receiveFullDiffError, 'file', @@ -1191,7 +1253,7 @@ describe('DiffsStoreActions', () => { mock.onGet(`${gl.TEST_HOST}/context`).replyOnce(200, ['test']); }); - it('dispatches receiveFullDiffSucess', done => { + it('commits the success and dispatches an action to expand the new lines', done => { const file = { context_lines_path: `${gl.TEST_HOST}/context`, file_path: 'test', @@ -1201,11 +1263,8 @@ describe('DiffsStoreActions', () => { fetchFullDiff, file, null, - [], - [ - { type: 'receiveFullDiffSucess', payload: { filePath: 'test' } }, - { type: 'setExpandedDiffLines', payload: { file, data: ['test'] } }, - ], + [{ type: types.RECEIVE_FULL_DIFF_SUCCESS, payload: { filePath: 'test' } }], + [{ type: 'setExpandedDiffLines', payload: { file, data: ['test'] } }], done, ); }); @@ -1243,11 +1302,8 @@ describe('DiffsStoreActions', () => { toggleFullDiff, 'test', state, - [], - [ - { type: 'requestFullDiff', payload: 'test' }, - { type: 'fetchFullDiff', payload: state.diffFiles[0] }, - ], + [{ type: types.REQUEST_FULL_DIFF, payload: 'test' }], + [{ type: 'fetchFullDiff', payload: state.diffFiles[0] }], done, ); }); @@ -1255,7 +1311,6 @@ describe('DiffsStoreActions', () => { describe('switchToFullDiffFromRenamedFile', () => { const SUCCESS_URL = 'fakehost/context.success'; - const ERROR_URL = 'fakehost/context.error'; const testFilePath = 'testpath'; const updatedViewerName = 'testviewer'; const preparedLine = { prepared: 'in-a-test' }; @@ -1311,27 +1366,6 @@ describe('DiffsStoreActions', () => { }, ); }); - - describe('error', () => { - beforeEach(() => { - renamedFile = { ...testFile, context_lines_path: ERROR_URL }; - mock.onGet(ERROR_URL).reply(500); - }); - - it('dispatches the error handling action', () => { - const rejected = testAction( - switchToFullDiffFromRenamedFile, - { diffFile: renamedFile }, - null, - [], - [{ type: 'receiveFullDiffError', payload: testFilePath }], - ); - - return rejected.catch(error => - expect(error).toEqual(new Error('Request failed with status code 500')), - ); - }); - }); }); describe('setFileCollapsed', () => { diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js index 641373e666f..891de45e268 100644 --- a/spec/frontend/diffs/store/utils_spec.js +++ b/spec/frontend/diffs/store/utils_spec.js @@ -14,9 +14,11 @@ import { } from '~/diffs/constants'; import { MERGE_REQUEST_NOTEABLE_TYPE } from '~/notes/constants'; import diffFileMockData from '../mock_data/diff_file'; +import { diffMetadata } from '../mock_data/diff_metadata'; import { noteableDataMock } from '../../notes/mock_data'; const getDiffFileMock = () => JSON.parse(JSON.stringify(diffFileMockData)); +const getDiffMetadataMock = () => JSON.parse(JSON.stringify(diffMetadata)); describe('DiffsStoreUtils', () => { describe('findDiffFile', () => { @@ -187,6 +189,7 @@ describe('DiffsStoreUtils', () => { }, diffViewType: PARALLEL_DIFF_VIEW_TYPE, linePosition: LINE_POSITION_LEFT, + lineRange: { start_line_code: 'abc_1_1', end_line_code: 'abc_2_2' }, }; const position = JSON.stringify({ @@ -198,6 +201,7 @@ describe('DiffsStoreUtils', () => { position_type: TEXT_DIFF_POSITION_TYPE, old_line: options.noteTargetLine.old_line, new_line: options.noteTargetLine.new_line, + line_range: options.lineRange, }); const postData = { @@ -428,112 +432,177 @@ describe('DiffsStoreUtils', () => { }); describe('prepareDiffData', () => { - let mock; - let preparedDiff; - let splitInlineDiff; - let splitParallelDiff; - let completedDiff; + describe('for regular diff files', () => { + let mock; + let preparedDiff; + let splitInlineDiff; + let splitParallelDiff; + let completedDiff; + + beforeEach(() => { + mock = getDiffFileMock(); + + preparedDiff = { diff_files: [mock] }; + splitInlineDiff = { + diff_files: [{ ...mock, parallel_diff_lines: undefined }], + }; + splitParallelDiff = { + diff_files: [{ ...mock, highlighted_diff_lines: undefined }], + }; + completedDiff = { + diff_files: [{ ...mock, highlighted_diff_lines: undefined }], + }; - beforeEach(() => { - mock = getDiffFileMock(); - preparedDiff = { diff_files: [mock] }; - splitInlineDiff = { - diff_files: [{ ...mock, parallel_diff_lines: undefined }], - }; - splitParallelDiff = { - diff_files: [{ ...mock, highlighted_diff_lines: undefined }], - }; - completedDiff = { - diff_files: [{ ...mock, highlighted_diff_lines: undefined }], - }; + preparedDiff.diff_files = utils.prepareDiffData(preparedDiff); + splitInlineDiff.diff_files = utils.prepareDiffData(splitInlineDiff); + splitParallelDiff.diff_files = utils.prepareDiffData(splitParallelDiff); + completedDiff.diff_files = utils.prepareDiffData(completedDiff, [mock]); + }); - preparedDiff.diff_files = utils.prepareDiffData(preparedDiff); - splitInlineDiff.diff_files = utils.prepareDiffData(splitInlineDiff); - splitParallelDiff.diff_files = utils.prepareDiffData(splitParallelDiff); - completedDiff.diff_files = utils.prepareDiffData(completedDiff, [mock]); - }); + it('sets the renderIt and collapsed attribute on files', () => { + const firstParallelDiffLine = preparedDiff.diff_files[0].parallel_diff_lines[2]; - it('sets the renderIt and collapsed attribute on files', () => { - const firstParallelDiffLine = preparedDiff.diff_files[0].parallel_diff_lines[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.rich_text.charAt(0); - 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.rich_text.charAt(0); + expect(firstParallelChar).not.toBe(' '); + expect(firstParallelChar).not.toBe('+'); + expect(firstParallelChar).not.toBe('-'); - expect(firstParallelChar).not.toBe(' '); - expect(firstParallelChar).not.toBe('+'); - expect(firstParallelChar).not.toBe('-'); + const checkLine = preparedDiff.diff_files[0].highlighted_diff_lines[0]; - const checkLine = preparedDiff.diff_files[0].highlighted_diff_lines[0]; + expect(checkLine.discussions.length).toBe(0); + expect(checkLine).not.toHaveAttr('text'); + const firstChar = checkLine.rich_text.charAt(0); - expect(checkLine.discussions.length).toBe(0); - expect(checkLine).not.toHaveAttr('text'); - const firstChar = checkLine.rich_text.charAt(0); + expect(firstChar).not.toBe(' '); + expect(firstChar).not.toBe('+'); + expect(firstChar).not.toBe('-'); - expect(firstChar).not.toBe(' '); - expect(firstChar).not.toBe('+'); - expect(firstChar).not.toBe('-'); + expect(preparedDiff.diff_files[0].renderIt).toBeTruthy(); + expect(preparedDiff.diff_files[0].collapsed).toBeFalsy(); + }); - expect(preparedDiff.diff_files[0].renderIt).toBeTruthy(); - expect(preparedDiff.diff_files[0].collapsed).toBeFalsy(); - }); + it('adds line_code to all lines', () => { + expect( + preparedDiff.diff_files[0].parallel_diff_lines.filter(line => !line.line_code), + ).toHaveLength(0); + }); - it('adds line_code to all lines', () => { - expect( - preparedDiff.diff_files[0].parallel_diff_lines.filter(line => !line.line_code), - ).toHaveLength(0); - }); + it('uses right line code if left has none', () => { + const firstLine = preparedDiff.diff_files[0].parallel_diff_lines[0]; - it('uses right line code if left has none', () => { - const firstLine = preparedDiff.diff_files[0].parallel_diff_lines[0]; + expect(firstLine.line_code).toEqual(firstLine.right.line_code); + }); - expect(firstLine.line_code).toEqual(firstLine.right.line_code); - }); + it('guarantees an empty array for both diff styles', () => { + expect(splitInlineDiff.diff_files[0].parallel_diff_lines.length).toEqual(0); + expect(splitInlineDiff.diff_files[0].highlighted_diff_lines.length).toBeGreaterThan(0); + expect(splitParallelDiff.diff_files[0].parallel_diff_lines.length).toBeGreaterThan(0); + expect(splitParallelDiff.diff_files[0].highlighted_diff_lines.length).toEqual(0); + }); - it('guarantees an empty array for both diff styles', () => { - expect(splitInlineDiff.diff_files[0].parallel_diff_lines.length).toEqual(0); - expect(splitInlineDiff.diff_files[0].highlighted_diff_lines.length).toBeGreaterThan(0); - expect(splitParallelDiff.diff_files[0].parallel_diff_lines.length).toBeGreaterThan(0); - expect(splitParallelDiff.diff_files[0].highlighted_diff_lines.length).toEqual(0); - }); + it('merges existing diff files with newly loaded diff files to ensure split diffs are eventually completed', () => { + expect(completedDiff.diff_files.length).toEqual(1); + expect(completedDiff.diff_files[0].parallel_diff_lines.length).toBeGreaterThan(0); + expect(completedDiff.diff_files[0].highlighted_diff_lines.length).toBeGreaterThan(0); + }); - it('merges existing diff files with newly loaded diff files to ensure split diffs are eventually completed', () => { - expect(completedDiff.diff_files.length).toEqual(1); - expect(completedDiff.diff_files[0].parallel_diff_lines.length).toBeGreaterThan(0); - expect(completedDiff.diff_files[0].highlighted_diff_lines.length).toBeGreaterThan(0); - }); + it('leaves files in the existing state', () => { + const priorFiles = [mock]; + const fakeNewFile = { + ...mock, + content_sha: 'ABC', + file_hash: 'DEF', + }; + const updatedFilesList = utils.prepareDiffData({ diff_files: [fakeNewFile] }, priorFiles); - it('leaves files in the existing state', () => { - const priorFiles = [mock]; - const fakeNewFile = { - ...mock, - content_sha: 'ABC', - file_hash: 'DEF', - }; - const updatedFilesList = utils.prepareDiffData({ diff_files: [fakeNewFile] }, priorFiles); + expect(updatedFilesList).toEqual([mock, fakeNewFile]); + }); - expect(updatedFilesList).toEqual([mock, fakeNewFile]); + it('completes an existing split diff without overwriting existing diffs', () => { + // The current state has a file that has only loaded inline lines + const priorFiles = [{ ...mock, parallel_diff_lines: [] }]; + // The next (batch) load loads two files: the other half of that file, and a new file + const fakeBatch = [ + { ...mock, highlighted_diff_lines: undefined }, + { ...mock, highlighted_diff_lines: undefined, content_sha: 'ABC', file_hash: 'DEF' }, + ]; + const updatedFilesList = utils.prepareDiffData({ diff_files: fakeBatch }, priorFiles); + + expect(updatedFilesList).toEqual([ + mock, + expect.objectContaining({ + content_sha: 'ABC', + file_hash: 'DEF', + }), + ]); + }); }); - it('completes an existing split diff without overwriting existing diffs', () => { - // The current state has a file that has only loaded inline lines - const priorFiles = [{ ...mock, parallel_diff_lines: [] }]; - // The next (batch) load loads two files: the other half of that file, and a new file - const fakeBatch = [ - { ...mock, highlighted_diff_lines: undefined }, - { ...mock, highlighted_diff_lines: undefined, content_sha: 'ABC', file_hash: 'DEF' }, - ]; - const updatedFilesList = utils.prepareDiffData({ diff_files: fakeBatch }, priorFiles); + describe('for diff metadata', () => { + let mock; + let preparedDiffFiles; - expect(updatedFilesList).toEqual([ - mock, - expect.objectContaining({ - content_sha: 'ABC', - file_hash: 'DEF', - }), - ]); + beforeEach(() => { + mock = getDiffMetadataMock(); + + preparedDiffFiles = utils.prepareDiffData(mock); + }); + + it('sets the renderIt and collapsed attribute on files', () => { + expect(preparedDiffFiles[0].renderIt).toBeTruthy(); + expect(preparedDiffFiles[0].collapsed).toBeFalsy(); + }); + + it('guarantees an empty array of lines for both diff styles', () => { + expect(preparedDiffFiles[0].parallel_diff_lines.length).toEqual(0); + expect(preparedDiffFiles[0].highlighted_diff_lines.length).toEqual(0); + }); + + it('leaves files in the existing state', () => { + const fileMock = getDiffFileMock(); + const metaData = getDiffMetadataMock(); + const priorFiles = [fileMock]; + const updatedFilesList = utils.prepareDiffData(metaData, priorFiles); + + expect(updatedFilesList.length).toEqual(2); + expect(updatedFilesList[0]).toEqual(fileMock); + }); + + it('adds a new file to the file that already exists in state', () => { + // This is actually buggy behavior: + // Because the metadata doesn't include a content_sha, + // the de-duplicator in prepareDiffData doesn't realize it + // should combine these two. + + // This buggy behavior hasn't caused a defect YET, because + // `diffs_metadata.json` is only called the first time the + // diffs app starts up, which is: + // - after a fresh page load + // - after you switch to the changes tab *the first time* + + // This test should begin FAILING and can be reversed to check + // for just a single file when this is implemented: + // https://gitlab.com/groups/gitlab-org/-/epics/2852#note_304803233 + + const fileMock = getDiffFileMock(); + const metaMock = getDiffMetadataMock(); + const priorFiles = [{ ...fileMock }]; + const updatedFilesList = utils.prepareDiffData(metaMock, priorFiles); + + expect(updatedFilesList).toEqual([ + fileMock, + { + ...metaMock.diff_files[0], + highlighted_diff_lines: [], + parallel_diff_lines: [], + }, + ]); + }); }); }); diff --git a/spec/frontend/diffs/utils/uuids_spec.js b/spec/frontend/diffs/utils/uuids_spec.js new file mode 100644 index 00000000000..79d3ebadd4f --- /dev/null +++ b/spec/frontend/diffs/utils/uuids_spec.js @@ -0,0 +1,92 @@ +import { uuids } from '~/diffs/utils/uuids'; + +const HEX = /[a-f0-9]/i; +const HEX_RE = HEX.source; +const UUIDV4 = new RegExp( + `${HEX_RE}{8}-${HEX_RE}{4}-4${HEX_RE}{3}-[89ab]${HEX_RE}{3}-${HEX_RE}{12}`, + 'i', +); + +describe('UUIDs Util', () => { + describe('uuids', () => { + const SEQUENCE_FOR_GITLAB_SEED = [ + 'a1826a44-316c-480e-a93d-8cdfeb36617c', + 'e049db1f-a4cf-4cba-aa60-6d95e3b547dc', + '6e3c737c-13a7-4380-b17d-601f187d7e69', + 'bee5cc7f-c486-45c0-8ad3-d1ac5402632d', + 'af248c9f-a3a6-4d4f-a311-fe151ffab25a', + ]; + const SEQUENCE_FOR_12345_SEED = [ + 'edfb51e2-e3e1-4de5-90fd-fd1d21760881', + '2f154da4-0a2d-4da9-b45e-0ffed391517e', + '91566d65-8836-4222-9875-9e1df4d0bb01', + 'f6ea6c76-7640-4d71-a736-9d3bec7a1a8e', + 'bfb85869-5fb9-4c5b-a750-5af727ac5576', + ]; + + it('returns version 4 UUIDs', () => { + expect(uuids()[0]).toMatch(UUIDV4); + }); + + it('outputs an array of UUIDs', () => { + const ids = uuids({ count: 11 }); + + expect(ids.length).toEqual(11); + expect(ids.every(id => UUIDV4.test(id))).toEqual(true); + }); + + it.each` + seeds | uuid + ${['some', 'special', 'seed']} | ${'6fa53e51-0f70-4072-9c84-1c1eee1b9934'} + ${['magic']} | ${'fafae8cd-7083-44f3-b82d-43b30bd27486'} + ${['seeded']} | ${'e06ed291-46c5-4e42-836b-e7c772d48b49'} + ${['GitLab']} | ${'a1826a44-316c-480e-a93d-8cdfeb36617c'} + ${['JavaScript']} | ${'12dfb297-1560-4c38-9775-7178ef8472fb'} + ${[99, 169834, 2619]} | ${'3ecc8ad6-5b7c-4c9b-94a8-c7271c2fa083'} + ${[12]} | ${'2777374b-723b-469b-bd73-e586df964cfd'} + ${[9876, 'mixed!', 7654]} | ${'865212e0-4a16-4934-96f9-103cf36a6931'} + ${[123, 1234, 12345, 6]} | ${'40aa2ee6-0a11-4e67-8f09-72f5eba04244'} + ${[0]} | ${'8c7f0aac-97c4-4a2f-b716-a675d821ccc0'} + `( + 'should always output the UUID $uuid when the options.seeds argument is $seeds', + ({ uuid, seeds }) => { + expect(uuids({ seeds })[0]).toEqual(uuid); + }, + ); + + describe('unseeded UUID randomness', () => { + const nonRandom = Array(6) + .fill(0) + .map((_, i) => uuids({ seeds: [i] })[0]); + const random = uuids({ count: 6 }); + const moreRandom = uuids({ count: 6 }); + + it('is different from a seeded result', () => { + random.forEach((id, i) => { + expect(id).not.toEqual(nonRandom[i]); + }); + }); + + it('is different from other random results', () => { + random.forEach((id, i) => { + expect(id).not.toEqual(moreRandom[i]); + }); + }); + + it('never produces any duplicates', () => { + expect(new Set(random).size).toEqual(random.length); + }); + }); + + it.each` + seed | sequence + ${'GitLab'} | ${SEQUENCE_FOR_GITLAB_SEED} + ${12345} | ${SEQUENCE_FOR_12345_SEED} + `( + 'should output the same sequence of UUIDs for the given seed "$seed"', + ({ seed, sequence }) => { + expect(uuids({ seeds: [seed], count: 5 })).toEqual(sequence); + }, + ); + }); +}); |