diff options
Diffstat (limited to 'spec/frontend/diffs')
26 files changed, 1191 insertions, 621 deletions
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index ac046ddc203..cd3a6aa0e28 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -1,6 +1,6 @@ import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlPagination } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import { TEST_HOST } from 'spec/test_constants'; import Mousetrap from 'mousetrap'; @@ -9,6 +9,7 @@ import NoChanges from '~/diffs/components/no_changes.vue'; import DiffFile from '~/diffs/components/diff_file.vue'; import CompareVersions from '~/diffs/components/compare_versions.vue'; import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue'; +import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vue'; import CommitWidget from '~/diffs/components/commit_widget.vue'; import TreeList from '~/diffs/components/tree_list.vue'; import { INLINE_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE } from '~/diffs/constants'; @@ -22,6 +23,10 @@ const TEST_ENDPOINT = `${TEST_HOST}/diff/endpoint`; const COMMIT_URL = '[BASE URL]/OLD'; const UPDATED_COMMIT_URL = '[BASE URL]/NEW'; +function getCollapsedFilesWarning(wrapper) { + return wrapper.find(CollapsedFilesWarning); +} + describe('diffs/components/app', () => { const oldMrTabs = window.mrTabs; let store; @@ -108,7 +113,6 @@ describe('diffs/components/app', () => { }; jest.spyOn(window, 'requestIdleCallback').mockImplementation(fn => fn()); createComponent(); - jest.spyOn(wrapper.vm, 'fetchDiffFiles').mockImplementation(fetchResolver); jest.spyOn(wrapper.vm, 'fetchDiffFilesMeta').mockImplementation(fetchResolver); jest.spyOn(wrapper.vm, 'fetchDiffFilesBatch').mockImplementation(fetchResolver); jest.spyOn(wrapper.vm, 'fetchCoverageFiles').mockImplementation(fetchResolver); @@ -139,37 +143,21 @@ describe('diffs/components/app', () => { parallel_diff_lines: ['line'], }; - function expectFetchToOccur({ - vueInstance, - done = () => {}, - batch = false, - existingFiles = 1, - } = {}) { + function expectFetchToOccur({ vueInstance, done = () => {}, existingFiles = 1 } = {}) { vueInstance.$nextTick(() => { expect(vueInstance.diffFiles.length).toEqual(existingFiles); - - if (!batch) { - expect(vueInstance.fetchDiffFiles).toHaveBeenCalled(); - expect(vueInstance.fetchDiffFilesBatch).not.toHaveBeenCalled(); - } else { - expect(vueInstance.fetchDiffFiles).not.toHaveBeenCalled(); - expect(vueInstance.fetchDiffFilesBatch).toHaveBeenCalled(); - } + expect(vueInstance.fetchDiffFilesBatch).toHaveBeenCalled(); done(); }); } - beforeEach(() => { - wrapper.vm.glFeatures.singleMrDiffView = true; - }); - it('fetches diffs if it has none', done => { wrapper.vm.isLatestVersion = () => false; store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: false, existingFiles: 0, done }); + expectFetchToOccur({ vueInstance: wrapper.vm, existingFiles: 0, done }); }); it('fetches diffs if it has both view styles, but no lines in either', done => { @@ -200,89 +188,46 @@ describe('diffs/components/app', () => { }); it('fetches batch diffs if it has none', done => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, existingFiles: 0, done }); + expectFetchToOccur({ vueInstance: wrapper.vm, existingFiles: 0, done }); }); it('fetches batch diffs if it has both view styles, but no lines in either', done => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffFiles.push(noLinesDiff); store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, done }); + expectFetchToOccur({ vueInstance: wrapper.vm, done }); }); it('fetches batch diffs if it only has inline view style', done => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffFiles.push(inlineLinesDiff); store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, done }); + expectFetchToOccur({ vueInstance: wrapper.vm, done }); }); it('fetches batch diffs if it only has parallel view style', done => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffFiles.push(parallelLinesDiff); store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, done }); - }); - - it('does not fetch diffs if it has already fetched both styles of diff', () => { - wrapper.vm.glFeatures.diffsBatchLoad = false; - - store.state.diffs.diffFiles.push(fullDiff); - store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - - expect(wrapper.vm.diffFiles.length).toEqual(1); - expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled(); - expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled(); + expectFetchToOccur({ vueInstance: wrapper.vm, done }); }); it('does not fetch batch diffs if it has already fetched both styles of diff', () => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffFiles.push(fullDiff); store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); expect(wrapper.vm.diffFiles.length).toEqual(1); - expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled(); expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled(); }); }); - it('calls fetchDiffFiles if diffsBatchLoad is not enabled', done => { - expect(wrapper.vm.diffFilesLength).toEqual(0); - wrapper.vm.glFeatures.diffsBatchLoad = false; - wrapper.vm.fetchData(false); - - expect(wrapper.vm.fetchDiffFiles).toHaveBeenCalled(); - setImmediate(() => { - expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled(); - expect(wrapper.vm.fetchDiffFilesMeta).not.toHaveBeenCalled(); - expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled(); - expect(wrapper.vm.fetchCoverageFiles).toHaveBeenCalled(); - expect(wrapper.vm.unwatchDiscussions).toHaveBeenCalled(); - expect(wrapper.vm.diffFilesLength).toEqual(100); - expect(wrapper.vm.unwatchRetrievingBatches).toHaveBeenCalled(); - - done(); - }); - }); - it('calls batch methods if diffsBatchLoad is enabled, and not latest version', done => { expect(wrapper.vm.diffFilesLength).toEqual(0); - wrapper.vm.glFeatures.diffsBatchLoad = true; wrapper.vm.isLatestVersion = () => false; wrapper.vm.fetchData(false); - expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled(); setImmediate(() => { expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled(); expect(wrapper.vm.fetchDiffFilesMeta).toHaveBeenCalled(); @@ -297,10 +242,8 @@ describe('diffs/components/app', () => { it('calls batch methods if diffsBatchLoad is enabled, and latest version', done => { expect(wrapper.vm.diffFilesLength).toEqual(0); - wrapper.vm.glFeatures.diffsBatchLoad = true; wrapper.vm.fetchData(false); - expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled(); setImmediate(() => { expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled(); expect(wrapper.vm.fetchDiffFilesMeta).toHaveBeenCalled(); @@ -320,7 +263,7 @@ describe('diffs/components/app', () => { state.diffs.isParallelView = false; }); - expect(wrapper.contains('.container-limited.limit-container-width')).toBe(true); + expect(wrapper.find('.container-limited.limit-container-width').exists()).toBe(true); }); it('does not add container-limiting classes when showFileTree is false with inline diffs', () => { @@ -329,7 +272,7 @@ describe('diffs/components/app', () => { state.diffs.isParallelView = false; }); - expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false); + expect(wrapper.find('.container-limited.limit-container-width').exists()).toBe(false); }); it('does not add container-limiting classes when isFluidLayout', () => { @@ -337,7 +280,7 @@ describe('diffs/components/app', () => { state.diffs.isParallelView = false; }); - expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false); + expect(wrapper.find('.container-limited.limit-container-width').exists()).toBe(false); }); it('displays loading icon on loading', () => { @@ -345,7 +288,7 @@ describe('diffs/components/app', () => { state.diffs.isLoading = true; }); - expect(wrapper.contains(GlLoadingIcon)).toBe(true); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); }); it('displays loading icon on batch loading', () => { @@ -353,20 +296,20 @@ describe('diffs/components/app', () => { state.diffs.isBatchLoading = true; }); - expect(wrapper.contains(GlLoadingIcon)).toBe(true); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); }); it('displays diffs container when not loading', () => { createComponent(); - expect(wrapper.contains(GlLoadingIcon)).toBe(false); - expect(wrapper.contains('#diffs')).toBe(true); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.find('#diffs').exists()).toBe(true); }); it('does not show commit info', () => { createComponent(); - expect(wrapper.contains('.blob-commit-info')).toBe(false); + expect(wrapper.find('.blob-commit-info').exists()).toBe(false); }); describe('row highlighting', () => { @@ -442,7 +385,7 @@ describe('diffs/components/app', () => { it('renders empty state when no diff files exist', () => { createComponent(); - expect(wrapper.contains(NoChanges)).toBe(true); + expect(wrapper.find(NoChanges).exists()).toBe(true); }); it('does not render empty state when diff files exist', () => { @@ -452,7 +395,7 @@ describe('diffs/components/app', () => { }); }); - expect(wrapper.contains(NoChanges)).toBe(false); + expect(wrapper.find(NoChanges).exists()).toBe(false); expect(wrapper.findAll(DiffFile).length).toBe(1); }); @@ -462,7 +405,7 @@ describe('diffs/components/app', () => { state.diffs.mergeRequestDiff = mergeRequestDiff; }); - expect(wrapper.contains(NoChanges)).toBe(false); + expect(wrapper.find(NoChanges).exists()).toBe(false); }); }); @@ -722,7 +665,7 @@ describe('diffs/components/app', () => { state.diffs.mergeRequestDiff = mergeRequestDiff; }); - expect(wrapper.contains(CompareVersions)).toBe(true); + expect(wrapper.find(CompareVersions).exists()).toBe(true); expect(wrapper.find(CompareVersions).props()).toEqual( expect.objectContaining({ mergeRequestDiffs: diffsMockData, @@ -730,24 +673,51 @@ describe('diffs/components/app', () => { ); }); - it('should render hidden files warning if render overflow warning is present', () => { - createComponent({}, ({ state }) => { - state.diffs.renderOverflowWarning = true; - state.diffs.realSize = '5'; - state.diffs.plainDiffPath = 'plain diff path'; - state.diffs.emailPatchPath = 'email patch path'; - state.diffs.size = 1; + describe('warnings', () => { + describe('hidden files', () => { + it('should render hidden files warning if render overflow warning is present', () => { + createComponent({}, ({ state }) => { + state.diffs.renderOverflowWarning = true; + state.diffs.realSize = '5'; + state.diffs.plainDiffPath = 'plain diff path'; + state.diffs.emailPatchPath = 'email patch path'; + state.diffs.size = 1; + }); + + expect(wrapper.find(HiddenFilesWarning).exists()).toBe(true); + expect(wrapper.find(HiddenFilesWarning).props()).toEqual( + expect.objectContaining({ + total: '5', + plainDiffPath: 'plain diff path', + emailPatchPath: 'email patch path', + visible: 1, + }), + ); + }); }); - expect(wrapper.contains(HiddenFilesWarning)).toBe(true); - expect(wrapper.find(HiddenFilesWarning).props()).toEqual( - expect.objectContaining({ - total: '5', - plainDiffPath: 'plain diff path', - emailPatchPath: 'email patch path', - visible: 1, - }), - ); + describe('collapsed files', () => { + it('should render the collapsed files warning if there are any collapsed files', () => { + createComponent({}, ({ state }) => { + state.diffs.diffFiles = [{ viewer: { collapsed: true } }]; + }); + + expect(getCollapsedFilesWarning(wrapper).exists()).toBe(true); + }); + + it('should not render the collapsed files warning if the user has dismissed the alert already', async () => { + createComponent({}, ({ state }) => { + state.diffs.diffFiles = [{ viewer: { collapsed: true } }]; + }); + + expect(getCollapsedFilesWarning(wrapper).exists()).toBe(true); + + wrapper.vm.collapsedWarningDismissed = true; + await wrapper.vm.$nextTick(); + + expect(getCollapsedFilesWarning(wrapper).exists()).toBe(false); + }); + }); }); it('should display commit widget if store has a commit', () => { @@ -757,7 +727,7 @@ describe('diffs/components/app', () => { }; }); - expect(wrapper.contains(CommitWidget)).toBe(true); + expect(wrapper.find(CommitWidget).exists()).toBe(true); }); it('should display diff file if there are diff files', () => { @@ -765,7 +735,7 @@ describe('diffs/components/app', () => { state.diffs.diffFiles.push({ sha: '123' }); }); - expect(wrapper.contains(DiffFile)).toBe(true); + expect(wrapper.find(DiffFile).exists()).toBe(true); }); it('should render tree list', () => { @@ -843,13 +813,16 @@ describe('diffs/components/app', () => { }); describe('pagination', () => { + const fileByFileNav = () => wrapper.find('[data-testid="file-by-file-navigation"]'); + const paginator = () => fileByFileNav().find(GlPagination); + it('sets previous button as disabled', () => { createComponent({ viewDiffsFileByFile: true }, ({ state }) => { state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' }); }); - expect(wrapper.find('[data-testid="singleFilePrevious"]').props('disabled')).toBe(true); - expect(wrapper.find('[data-testid="singleFileNext"]').props('disabled')).toBe(false); + expect(paginator().attributes('prevpage')).toBe(undefined); + expect(paginator().attributes('nextpage')).toBe('2'); }); it('sets next button as disabled', () => { @@ -858,17 +831,26 @@ describe('diffs/components/app', () => { state.diffs.currentDiffFileId = '312'; }); - expect(wrapper.find('[data-testid="singleFilePrevious"]').props('disabled')).toBe(false); - expect(wrapper.find('[data-testid="singleFileNext"]').props('disabled')).toBe(true); + expect(paginator().attributes('prevpage')).toBe('1'); + expect(paginator().attributes('nextpage')).toBe(undefined); + }); + + it("doesn't display when there's fewer than 2 files", () => { + createComponent({ viewDiffsFileByFile: true }, ({ state }) => { + state.diffs.diffFiles.push({ file_hash: '123' }); + state.diffs.currentDiffFileId = '123'; + }); + + expect(fileByFileNav().exists()).toBe(false); }); it.each` - currentDiffFileId | button | index - ${'123'} | ${'singleFileNext'} | ${1} - ${'312'} | ${'singleFilePrevious'} | ${0} + currentDiffFileId | targetFile + ${'123'} | ${2} + ${'312'} | ${1} `( - 'it calls navigateToDiffFileIndex with $index when $button is clicked', - ({ currentDiffFileId, button, index }) => { + 'it calls navigateToDiffFileIndex with $index when $link is clicked', + async ({ currentDiffFileId, targetFile }) => { createComponent({ viewDiffsFileByFile: true }, ({ state }) => { state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' }); state.diffs.currentDiffFileId = currentDiffFileId; @@ -876,11 +858,11 @@ describe('diffs/components/app', () => { jest.spyOn(wrapper.vm, 'navigateToDiffFileIndex'); - wrapper.find(`[data-testid="${button}"]`).vm.$emit('click'); + paginator().vm.$emit('input', targetFile); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.vm.navigateToDiffFileIndex).toHaveBeenCalledWith(index); - }); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.navigateToDiffFileIndex).toHaveBeenCalledWith(targetFile - 1); }, ); }); diff --git a/spec/frontend/diffs/components/collapsed_files_warning_spec.js b/spec/frontend/diffs/components/collapsed_files_warning_spec.js new file mode 100644 index 00000000000..670eab5472f --- /dev/null +++ b/spec/frontend/diffs/components/collapsed_files_warning_spec.js @@ -0,0 +1,88 @@ +import Vuex from 'vuex'; +import { shallowMount, mount, createLocalVue } from '@vue/test-utils'; +import createStore from '~/diffs/store/modules'; +import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vue'; +import { CENTERED_LIMITED_CONTAINER_CLASSES } from '~/diffs/constants'; + +const propsData = { + limited: true, + mergeable: true, + resolutionPath: 'a-path', +}; +const limitedClasses = CENTERED_LIMITED_CONTAINER_CLASSES.split(' '); + +describe('CollapsedFilesWarning', () => { + const localVue = createLocalVue(); + let store; + let wrapper; + + localVue.use(Vuex); + + const getAlertActionButton = () => + wrapper.find(CollapsedFilesWarning).find('button.gl-alert-action:first-child'); + const getAlertCloseButton = () => wrapper.find(CollapsedFilesWarning).find('button'); + + const createComponent = (props = {}, { full } = { full: false }) => { + const mounter = full ? mount : shallowMount; + store = new Vuex.Store({ + modules: { + diffs: createStore(), + }, + }); + + wrapper = mounter(CollapsedFilesWarning, { + propsData: { ...propsData, ...props }, + localVue, + store, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it.each` + limited | containerClasses + ${true} | ${limitedClasses} + ${false} | ${[]} + `( + 'has the correct container classes when limited is $limited', + ({ limited, containerClasses }) => { + createComponent({ limited }); + + expect(wrapper.classes()).toEqual(containerClasses); + }, + ); + + it.each` + present | dismissed + ${false} | ${true} + ${true} | ${false} + `('toggles the alert when dismissed is $dismissed', ({ present, dismissed }) => { + createComponent({ dismissed }); + + expect(wrapper.find('[data-testid="root"]').exists()).toBe(present); + }); + + it('dismisses the component when the alert "x" is clicked', async () => { + createComponent({}, { full: true }); + + expect(wrapper.find('[data-testid="root"]').exists()).toBe(true); + + getAlertCloseButton().element.click(); + + await wrapper.vm.$nextTick(); + + expect(wrapper.find('[data-testid="root"]').exists()).toBe(false); + }); + + it('triggers the expandAllFiles action when the alert action button is clicked', () => { + createComponent({}, { full: true }); + + jest.spyOn(wrapper.vm.$store, 'dispatch').mockReturnValue(undefined); + + getAlertActionButton().vm.$emit('click'); + + expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('diffs/expandAllFiles', undefined); + }); +}); diff --git a/spec/frontend/diffs/components/commit_item_spec.js b/spec/frontend/diffs/components/commit_item_spec.js index 0df951d43a7..c48445790f7 100644 --- a/spec/frontend/diffs/components/commit_item_spec.js +++ b/spec/frontend/diffs/components/commit_item_spec.js @@ -24,8 +24,7 @@ describe('diffs/components/commit_item', () => { const getTitleElement = () => wrapper.find('.commit-row-message.item-title'); const getDescElement = () => wrapper.find('pre.commit-row-description'); - const getDescExpandElement = () => - wrapper.find('.commit-content .text-expander.js-toggle-button'); + const getDescExpandElement = () => wrapper.find('.commit-content .js-toggle-button'); const getShaElement = () => wrapper.find('.commit-sha-group'); const getAvatarElement = () => wrapper.find('.user-avatar-link'); const getCommitterElement = () => wrapper.find('.committer'); diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js index 7fdbc791589..b3dfc71260c 100644 --- a/spec/frontend/diffs/components/compare_versions_spec.js +++ b/spec/frontend/diffs/components/compare_versions_spec.js @@ -2,7 +2,6 @@ import { trimText } from 'helpers/text_helper'; import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; import CompareVersionsComponent from '~/diffs/components/compare_versions.vue'; -import Icon from '~/vue_shared/components/icon.vue'; import { createStore } from '~/mr_notes/stores'; import diffsMockData from '../mock_data/merge_request_diffs'; import getDiffWithCommit from '../mock_data/diff_with_commit'; @@ -51,7 +50,7 @@ describe('CompareVersions', () => { expect(treeListBtn.exists()).toBe(true); expect(treeListBtn.attributes('title')).toBe('Hide file browser'); - expect(treeListBtn.find(Icon).props('name')).toBe('file-tree'); + expect(treeListBtn.props('icon')).toBe('file-tree'); }); it('should render comparison dropdowns with correct values', () => { diff --git a/spec/frontend/diffs/components/diff_content_spec.js b/spec/frontend/diffs/components/diff_content_spec.js index b78895f9e55..6d0120d888e 100644 --- a/spec/frontend/diffs/components/diff_content_spec.js +++ b/spec/frontend/diffs/components/diff_content_spec.js @@ -177,23 +177,19 @@ describe('DiffContent', () => { }); wrapper.find(NoteForm).vm.$emit('handleFormUpdate', noteStub); - expect(saveDiffDiscussionMock).toHaveBeenCalledWith( - expect.any(Object), - { - note: noteStub, - formData: { - noteableData: expect.any(Object), - diffFile: currentDiffFile, - positionType: IMAGE_DIFF_POSITION_TYPE, - x: undefined, - y: undefined, - width: undefined, - height: undefined, - noteableType: undefined, - }, + expect(saveDiffDiscussionMock).toHaveBeenCalledWith(expect.any(Object), { + note: noteStub, + formData: { + noteableData: expect.any(Object), + diffFile: currentDiffFile, + positionType: IMAGE_DIFF_POSITION_TYPE, + x: undefined, + y: undefined, + width: undefined, + height: undefined, + noteableType: undefined, }, - undefined, - ); + }); }); }); }); diff --git a/spec/frontend/diffs/components/diff_discussions_spec.js b/spec/frontend/diffs/components/diff_discussions_spec.js index 83becc7a20a..96b76183cee 100644 --- a/spec/frontend/diffs/components/diff_discussions_spec.js +++ b/spec/frontend/diffs/components/diff_discussions_spec.js @@ -1,9 +1,9 @@ import { mount, createLocalVue } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; import DiffDiscussions from '~/diffs/components/diff_discussions.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import NoteableDiscussion from '~/notes/components/noteable_discussion.vue'; import DiscussionNotes from '~/notes/components/discussion_notes.vue'; -import Icon from '~/vue_shared/components/icon.vue'; import { createStore } from '~/mr_notes/stores'; import '~/behaviors/markdown/render_gfm'; import discussionsMockData from '../mock_data/diff_discussions'; @@ -51,7 +51,7 @@ describe('DiffDiscussions', () => { const diffNotesToggle = findDiffNotesToggle(); expect(diffNotesToggle.exists()).toBe(true); - expect(diffNotesToggle.find(Icon).exists()).toBe(true); + expect(diffNotesToggle.find(GlIcon).exists()).toBe(true); expect(diffNotesToggle.classes('diff-notes-collapse')).toBe(true); }); diff --git a/spec/frontend/diffs/components/diff_expansion_cell_spec.js b/spec/frontend/diffs/components/diff_expansion_cell_spec.js index b8aca4ad86b..81e08f09f62 100644 --- a/spec/frontend/diffs/components/diff_expansion_cell_spec.js +++ b/spec/frontend/diffs/components/diff_expansion_cell_spec.js @@ -10,7 +10,6 @@ import diffFileMockData from '../mock_data/diff_file'; const EXPAND_UP_CLASS = '.js-unfold'; const EXPAND_DOWN_CLASS = '.js-unfold-down'; -const LINE_TO_USE = 5; const lineSources = { [INLINE_DIFF_VIEW_TYPE]: 'highlighted_diff_lines', [PARALLEL_DIFF_VIEW_TYPE]: 'parallel_diff_lines', @@ -66,7 +65,7 @@ describe('DiffExpansionCell', () => { beforeEach(() => { mockFile = cloneDeep(diffFileMockData); - mockLine = getLine(mockFile, INLINE_DIFF_VIEW_TYPE, LINE_TO_USE); + mockLine = getLine(mockFile, INLINE_DIFF_VIEW_TYPE, 8); store = createStore(); store.state.diffs.diffFiles = [mockFile]; jest.spyOn(store, 'dispatch').mockReturnValue(Promise.resolve()); @@ -88,7 +87,7 @@ describe('DiffExpansionCell', () => { const findExpandUp = () => vm.$el.querySelector(EXPAND_UP_CLASS); const findExpandDown = () => vm.$el.querySelector(EXPAND_DOWN_CLASS); - const findExpandAll = () => getByText(vm.$el, 'Show unchanged lines'); + const findExpandAll = () => getByText(vm.$el, 'Show all unchanged lines'); describe('top row', () => { it('should have "expand up" and "show all" option', () => { @@ -126,12 +125,12 @@ describe('DiffExpansionCell', () => { describe('any row', () => { [ - { diffViewType: INLINE_DIFF_VIEW_TYPE, file: { parallel_diff_lines: [] } }, - { diffViewType: PARALLEL_DIFF_VIEW_TYPE, file: { highlighted_diff_lines: [] } }, - ].forEach(({ diffViewType, file }) => { + { diffViewType: INLINE_DIFF_VIEW_TYPE, lineIndex: 8, file: { parallel_diff_lines: [] } }, + { diffViewType: PARALLEL_DIFF_VIEW_TYPE, lineIndex: 7, file: { highlighted_diff_lines: [] } }, + ].forEach(({ diffViewType, file, lineIndex }) => { describe(`with diffViewType (${diffViewType})`, () => { beforeEach(() => { - mockLine = getLine(mockFile, diffViewType, LINE_TO_USE); + mockLine = getLine(mockFile, diffViewType, lineIndex); store.state.diffs.diffFiles = [{ ...mockFile, ...file }]; store.state.diffs.diffViewType = diffViewType; }); @@ -189,10 +188,10 @@ describe('DiffExpansionCell', () => { }); it('on expand down clicked, dispatch loadMoreLines', () => { - mockFile[lineSources[diffViewType]][LINE_TO_USE + 1] = cloneDeep( - mockFile[lineSources[diffViewType]][LINE_TO_USE], + mockFile[lineSources[diffViewType]][lineIndex + 1] = cloneDeep( + mockFile[lineSources[diffViewType]][lineIndex], ); - const nextLine = getLine(mockFile, diffViewType, LINE_TO_USE + 1); + const nextLine = getLine(mockFile, diffViewType, lineIndex + 1); nextLine.meta_data.old_pos = 300; nextLine.meta_data.new_pos = 300; diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js index 671dced080c..a0cad32b9fb 100644 --- a/spec/frontend/diffs/components/diff_file_header_spec.js +++ b/spec/frontend/diffs/components/diff_file_header_spec.js @@ -1,9 +1,10 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; +import { GlIcon } from '@gitlab/ui'; +import { cloneDeep } from 'lodash'; import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; import EditButton from '~/diffs/components/edit_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; -import Icon from '~/vue_shared/components/icon.vue'; import diffDiscussionsMockData from '../mock_data/diff_discussions'; import { truncateSha } from '~/lib/utils/text_utility'; import { diffViewerModes } from '~/ide/constants'; @@ -26,12 +27,16 @@ const diffFile = Object.freeze( }), ); +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('DiffFileHeader component', () => { let wrapper; + let mockStoreConfig; const diffHasExpandedDiscussionsResultMock = jest.fn(); const diffHasDiscussionsResultMock = jest.fn(); - const mockStoreConfig = { + const defaultMockStoreConfig = { state: {}, modules: { diffs: { @@ -44,6 +49,7 @@ describe('DiffFileHeader component', () => { toggleFileDiscussions: jest.fn(), toggleFileDiscussionWrappers: jest.fn(), toggleFullDiff: jest.fn(), + toggleActiveFileByHash: jest.fn(), }, }, }, @@ -55,6 +61,8 @@ describe('DiffFileHeader component', () => { diffHasExpandedDiscussionsResultMock, ...Object.values(mockStoreConfig.modules.diffs.actions), ].forEach(mock => mock.mockReset()); + + wrapper.destroy(); }); const findHeader = () => wrapper.find({ ref: 'header' }); @@ -70,7 +78,7 @@ describe('DiffFileHeader component', () => { const findCollapseIcon = () => wrapper.find({ ref: 'collapseIcon' }); const findIconByName = iconName => { - const icons = wrapper.findAll(Icon).filter(w => w.props('name') === iconName); + const icons = wrapper.findAll(GlIcon).filter(w => w.props('name') === iconName); if (icons.length === 0) return icons; if (icons.length > 1) { throw new Error(`Multiple icons found for ${iconName}`); @@ -79,8 +87,7 @@ describe('DiffFileHeader component', () => { }; const createComponent = props => { - const localVue = createLocalVue(); - localVue.use(Vuex); + mockStoreConfig = cloneDeep(defaultMockStoreConfig); const store = new Vuex.Store(mockStoreConfig); wrapper = shallowMount(DiffFileHeader, { @@ -285,7 +292,7 @@ describe('DiffFileHeader component', () => { findToggleDiscussionsButton().vm.$emit('click'); expect( mockStoreConfig.modules.diffs.actions.toggleFileDiscussionWrappers, - ).toHaveBeenCalledWith(expect.any(Object), diffFile, undefined); + ).toHaveBeenCalledWith(expect.any(Object), diffFile); }); }); diff --git a/spec/frontend/diffs/components/diff_file_row_spec.js b/spec/frontend/diffs/components/diff_file_row_spec.js index afdd4bfb335..23adc8f9da4 100644 --- a/spec/frontend/diffs/components/diff_file_row_spec.js +++ b/spec/frontend/diffs/components/diff_file_row_spec.js @@ -7,9 +7,12 @@ import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue'; describe('Diff File Row component', () => { let wrapper; - const createComponent = (props = {}) => { + const createComponent = (props = {}, highlightCurrentDiffRow = false) => { wrapper = shallowMount(DiffFileRow, { propsData: { ...props }, + provide: { + glFeatures: { highlightCurrentDiffRow }, + }, }); }; @@ -56,6 +59,31 @@ describe('Diff File Row component', () => { ); }); + it.each` + features | fileType | isViewed | expected + ${{ highlightCurrentDiffRow: true }} | ${'blob'} | ${false} | ${'gl-font-weight-bold'} + ${{}} | ${'blob'} | ${true} | ${''} + ${{}} | ${'tree'} | ${false} | ${''} + ${{}} | ${'tree'} | ${true} | ${''} + `( + 'with (features="$features", fileType="$fileType", isViewed=$isViewed), sets fileClasses="$expected"', + ({ features, fileType, isViewed, expected }) => { + createComponent( + { + file: { + type: fileType, + fileHash: '#123456789', + }, + level: 0, + hideFileStats: false, + viewedFiles: isViewed ? { '#123456789': true } : {}, + }, + features.highlightCurrentDiffRow, + ); + expect(wrapper.find(FileRow).props('fileClasses')).toBe(expected); + }, + ); + describe('FileRowStats components', () => { it.each` type | hideFileStats | value | desc diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js index ead8bd79cdb..79f0f6bc327 100644 --- a/spec/frontend/diffs/components/diff_file_spec.js +++ b/spec/frontend/diffs/components/diff_file_spec.js @@ -45,7 +45,7 @@ describe('DiffFile', () => { vm.$nextTick() .then(() => { - expect(el.querySelectorAll('.line_content').length).toBe(5); + expect(el.querySelectorAll('.line_content').length).toBe(8); expect(el.querySelectorAll('.js-line-expansion-content').length).toBe(1); triggerEvent('.btn-clipboard'); }) @@ -90,8 +90,8 @@ describe('DiffFile', () => { vm.isCollapsed = true; vm.$nextTick(() => { - expect(vm.$el.innerText).toContain('This diff is collapsed'); - expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); + expect(vm.$el.innerText).toContain('This file is collapsed.'); + expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy(); done(); }); @@ -102,8 +102,8 @@ describe('DiffFile', () => { vm.isCollapsed = true; vm.$nextTick(() => { - expect(vm.$el.innerText).toContain('This diff is collapsed'); - expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); + expect(vm.$el.innerText).toContain('This file is collapsed.'); + expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy(); done(); }); @@ -121,28 +121,8 @@ describe('DiffFile', () => { vm.isCollapsed = true; vm.$nextTick(() => { - expect(vm.$el.innerText).toContain('This diff is collapsed'); - expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); - - done(); - }); - }); - - it('should auto-expand collapsed files when viewDiffsFileByFile is true', done => { - vm.$destroy(); - window.gon = { - features: { autoExpandCollapsedDiffs: true }, - }; - vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), { - file: JSON.parse(JSON.stringify(diffFileMockDataUnreadable)), - canCurrentUserFork: false, - viewDiffsFileByFile: true, - }).$mount(); - - vm.$nextTick(() => { - expect(vm.$el.innerText).not.toContain('This diff is collapsed'); - - window.gon = {}; + expect(vm.$el.innerText).toContain('This file is collapsed.'); + expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy(); done(); }); @@ -155,7 +135,7 @@ describe('DiffFile', () => { vm.file.viewer.name = diffViewerModes.renamed; vm.$nextTick(() => { - expect(vm.$el.innerText).not.toContain('This diff is collapsed'); + expect(vm.$el.innerText).not.toContain('This file is collapsed.'); done(); }); @@ -168,7 +148,7 @@ describe('DiffFile', () => { vm.file.viewer.name = diffViewerModes.mode_changed; vm.$nextTick(() => { - expect(vm.$el.innerText).not.toContain('This diff is collapsed'); + expect(vm.$el.innerText).not.toContain('This file is collapsed.'); done(); }); @@ -235,7 +215,7 @@ describe('DiffFile', () => { it('calls handleLoadCollapsedDiff if collapsed changed & file has no lines', done => { jest.spyOn(vm, 'handleLoadCollapsedDiff').mockImplementation(() => {}); - vm.file.highlighted_diff_lines = undefined; + vm.file.highlighted_diff_lines = []; vm.file.parallel_diff_lines = []; vm.isCollapsed = true; @@ -262,8 +242,8 @@ describe('DiffFile', () => { jest.spyOn(vm, 'handleLoadCollapsedDiff').mockImplementation(() => {}); - vm.file.highlighted_diff_lines = undefined; - vm.file.parallel_diff_lines = []; + vm.file.highlighted_diff_lines = []; + vm.file.parallel_diff_lines = undefined; vm.isCollapsed = true; vm.$nextTick() diff --git a/spec/frontend/diffs/components/diff_stats_spec.js b/spec/frontend/diffs/components/diff_stats_spec.js index 7a083fb6bde..4dcbb3ec332 100644 --- a/spec/frontend/diffs/components/diff_stats_spec.js +++ b/spec/frontend/diffs/components/diff_stats_spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; import DiffStats from '~/diffs/components/diff_stats.vue'; -import Icon from '~/vue_shared/components/icon.vue'; const TEST_ADDED_LINES = 100; const TEST_REMOVED_LINES = 200; @@ -53,7 +53,7 @@ describe('diff_stats', () => { describe('files changes', () => { const findIcon = name => wrapper - .findAll(Icon) + .findAll(GlIcon) .filter(c => c.attributes('name') === name) .at(0).element.parentNode; diff --git a/spec/frontend/diffs/components/image_diff_overlay_spec.js b/spec/frontend/diffs/components/image_diff_overlay_spec.js index accf0a972d0..5a88a3cabd1 100644 --- a/spec/frontend/diffs/components/image_diff_overlay_spec.js +++ b/spec/frontend/diffs/components/image_diff_overlay_spec.js @@ -1,8 +1,8 @@ import { shallowMount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue'; import { createStore } from '~/mr_notes/stores'; import { imageDiffDiscussions } from '../mock_data/diff_discussions'; -import Icon from '~/vue_shared/components/icon.vue'; describe('Diffs image diff overlay component', () => { const dimensions = { @@ -64,7 +64,7 @@ describe('Diffs image diff overlay component', () => { it('renders icon when showCommentIcon is true', () => { createComponent({ showCommentIcon: true }); - expect(wrapper.find(Icon).exists()).toBe(true); + expect(wrapper.find(GlIcon).exists()).toBe(true); }); it('sets badge comment positions', () => { diff --git a/spec/frontend/diffs/components/inline_diff_expansion_row_spec.js b/spec/frontend/diffs/components/inline_diff_expansion_row_spec.js index 90f012fbafe..81e5403d502 100644 --- a/spec/frontend/diffs/components/inline_diff_expansion_row_spec.js +++ b/spec/frontend/diffs/components/inline_diff_expansion_row_spec.js @@ -5,12 +5,13 @@ import InlineDiffExpansionRow from '~/diffs/components/inline_diff_expansion_row import diffFileMockData from '../mock_data/diff_file'; describe('InlineDiffExpansionRow', () => { - const matchLine = diffFileMockData.highlighted_diff_lines[5]; + const mockData = { ...diffFileMockData }; + const matchLine = mockData.highlighted_diff_lines.pop(); const createComponent = (options = {}) => { const cmp = Vue.extend(InlineDiffExpansionRow); const defaults = { - fileHash: diffFileMockData.file_hash, + fileHash: mockData.file_hash, contextLinesPath: 'contextLinesPath', line: matchLine, isTop: false, diff --git a/spec/frontend/diffs/components/inline_diff_table_row_spec.js b/spec/frontend/diffs/components/inline_diff_table_row_spec.js index f929f97b598..951b3f6258b 100644 --- a/spec/frontend/diffs/components/inline_diff_table_row_spec.js +++ b/spec/frontend/diffs/components/inline_diff_table_row_spec.js @@ -1,114 +1,317 @@ import { shallowMount } from '@vue/test-utils'; +import { TEST_HOST } from 'helpers/test_constants'; import { createStore } from '~/mr_notes/stores'; import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue'; +import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue'; import diffFileMockData from '../mock_data/diff_file'; +import discussionsMockData from '../mock_data/diff_discussions'; + +const TEST_USER_ID = 'abc123'; +const TEST_USER = { id: TEST_USER_ID }; describe('InlineDiffTableRow', () => { let wrapper; - let vm; + let store; const thisLine = diffFileMockData.highlighted_diff_lines[0]; - beforeEach(() => { + const createComponent = (props = {}, propsStore = store) => { wrapper = shallowMount(InlineDiffTableRow, { - store: createStore(), + store: propsStore, propsData: { line: thisLine, fileHash: diffFileMockData.file_hash, filePath: diffFileMockData.file_path, contextLinesPath: 'contextLinesPath', isHighlighted: false, + ...props, }, }); - vm = wrapper.vm; + }; + + const setWindowLocation = value => { + Object.defineProperty(window, 'location', { + writable: true, + value, + }); + }; + + beforeEach(() => { + store = createStore(); + store.state.notes.userData = TEST_USER; + }); + + afterEach(() => { + wrapper.destroy(); }); - it('does not add hll class to line content when line does not match highlighted row', done => { - vm.$nextTick() - .then(() => { - expect(wrapper.find('.line_content').classes('hll')).toBe(false); - }) - .then(done) - .catch(done.fail); + it('does not add hll class to line content when line does not match highlighted row', () => { + createComponent(); + expect(wrapper.find('.line_content').classes('hll')).toBe(false); }); - it('adds hll class to lineContent when line is the highlighted row', done => { - vm.$nextTick() - .then(() => { - vm.$store.state.diffs.highlightedRow = thisLine.line_code; - - return vm.$nextTick(); - }) - .then(() => { - expect(wrapper.find('.line_content').classes('hll')).toBe(true); - }) - .then(done) - .catch(done.fail); + it('adds hll class to lineContent when line is the highlighted row', () => { + store.state.diffs.highlightedRow = thisLine.line_code; + createComponent({}, store); + expect(wrapper.find('.line_content').classes('hll')).toBe(true); }); it('adds hll class to lineContent when line is part of a multiline comment', () => { - wrapper.setProps({ isCommented: true }); - return vm.$nextTick().then(() => { - expect(wrapper.find('.line_content').classes('hll')).toBe(true); - }); + createComponent({ isCommented: true }); + expect(wrapper.find('.line_content').classes('hll')).toBe(true); }); describe('sets coverage title and class', () => { - it('for lines with coverage', done => { - vm.$nextTick() - .then(() => { - const name = diffFileMockData.file_path; - const line = thisLine.new_line; - - vm.$store.state.diffs.coverageFiles = { files: { [name]: { [line]: 5 } } }; - - return vm.$nextTick(); - }) - .then(() => { - const coverage = wrapper.find('.line-coverage'); - - expect(coverage.attributes('title')).toContain('Test coverage: 5 hits'); - expect(coverage.classes('coverage')).toBe(true); - }) - .then(done) - .catch(done.fail); + it('for lines with coverage', () => { + const name = diffFileMockData.file_path; + const line = thisLine.new_line; + + store.state.diffs.coverageFiles = { files: { [name]: { [line]: 5 } } }; + createComponent({}, store); + const coverage = wrapper.find('.line-coverage'); + + expect(coverage.attributes('title')).toContain('Test coverage: 5 hits'); + expect(coverage.classes('coverage')).toBe(true); + }); + + it('for lines without coverage', () => { + const name = diffFileMockData.file_path; + const line = thisLine.new_line; + + store.state.diffs.coverageFiles = { files: { [name]: { [line]: 0 } } }; + createComponent({}, store); + const coverage = wrapper.find('.line-coverage'); + + expect(coverage.attributes('title')).toContain('No test coverage'); + expect(coverage.classes('no-coverage')).toBe(true); + }); + + it('for unknown lines', () => { + store.state.diffs.coverageFiles = {}; + createComponent({}, store); + + const coverage = wrapper.find('.line-coverage'); + + expect(coverage.attributes('title')).toBeUndefined(); + expect(coverage.classes('coverage')).toBe(false); + expect(coverage.classes('no-coverage')).toBe(false); + }); + }); + + describe('Table Cells', () => { + const findNewTd = () => wrapper.find({ ref: 'newTd' }); + const findOldTd = () => wrapper.find({ ref: 'oldTd' }); + + describe('td', () => { + it('highlights when isHighlighted true', () => { + store.state.diffs.highlightedRow = thisLine.line_code; + createComponent({}, store); + + expect(findNewTd().classes()).toContain('hll'); + expect(findOldTd().classes()).toContain('hll'); + }); + + it('does not highlight when isHighlighted false', () => { + createComponent(); + + expect(findNewTd().classes()).not.toContain('hll'); + expect(findOldTd().classes()).not.toContain('hll'); + }); + }); + + describe('comment button', () => { + const findNoteButton = () => wrapper.find({ ref: 'addDiffNoteButton' }); + + it.each` + userData | query | mergeRefHeadComments | expectation + ${TEST_USER} | ${'diff_head=false'} | ${false} | ${true} + ${TEST_USER} | ${'diff_head=true'} | ${true} | ${true} + ${TEST_USER} | ${'diff_head=true'} | ${false} | ${false} + ${null} | ${''} | ${true} | ${false} + `( + 'exists is $expectation - with userData ($userData) query ($query)', + ({ userData, query, mergeRefHeadComments, expectation }) => { + store.state.notes.userData = userData; + gon.features = { mergeRefHeadComments }; + setWindowLocation({ href: `${TEST_HOST}?${query}` }); + createComponent({}, store); + + expect(findNoteButton().exists()).toBe(expectation); + }, + ); + + it.each` + isHover | line | expectation + ${true} | ${{ ...thisLine, discussions: [] }} | ${true} + ${false} | ${{ ...thisLine, discussions: [] }} | ${false} + ${true} | ${{ ...thisLine, type: 'context', discussions: [] }} | ${false} + ${true} | ${{ ...thisLine, type: 'old-nonewline', discussions: [] }} | ${false} + ${true} | ${{ ...thisLine, discussions: [{}] }} | ${false} + `('visible is $expectation - line ($line)', ({ isHover, line, expectation }) => { + createComponent({ line }); + wrapper.setData({ isHover }); + + return wrapper.vm.$nextTick().then(() => { + expect(findNoteButton().isVisible()).toBe(expectation); + }); + }); + + it.each` + disabled | commentsDisabled + ${'disabled'} | ${true} + ${undefined} | ${false} + `( + 'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled', + ({ disabled, commentsDisabled }) => { + createComponent({ + line: { ...thisLine, commentsDisabled }, + }); + + wrapper.setData({ isHover: true }); + + return wrapper.vm.$nextTick().then(() => { + expect(findNoteButton().attributes('disabled')).toBe(disabled); + }); + }, + ); + + const symlinkishFileTooltip = + 'Commenting on symbolic links that replace or are replaced by files is currently not supported.'; + const realishFileTooltip = + 'Commenting on files that replace or are replaced by symbolic links is currently not supported.'; + const otherFileTooltip = 'Add a comment to this line'; + const findTooltip = () => wrapper.find({ ref: 'addNoteTooltip' }); + + it.each` + tooltip | commentsDisabled + ${symlinkishFileTooltip} | ${{ wasSymbolic: true }} + ${symlinkishFileTooltip} | ${{ isSymbolic: true }} + ${realishFileTooltip} | ${{ wasReal: true }} + ${realishFileTooltip} | ${{ isReal: true }} + ${otherFileTooltip} | ${false} + `( + 'has the correct tooltip when commentsDisabled=$commentsDisabled', + ({ tooltip, commentsDisabled }) => { + createComponent({ + line: { ...thisLine, commentsDisabled }, + }); + + wrapper.setData({ isHover: true }); + + return wrapper.vm.$nextTick().then(() => { + expect(findTooltip().attributes('title')).toBe(tooltip); + }); + }, + ); }); - it('for lines without coverage', done => { - vm.$nextTick() - .then(() => { - const name = diffFileMockData.file_path; - const line = thisLine.new_line; + describe('line number', () => { + const findLineNumberOld = () => wrapper.find({ ref: 'lineNumberRefOld' }); + const findLineNumberNew = () => wrapper.find({ ref: 'lineNumberRefNew' }); + + it('renders line numbers in correct cells', () => { + createComponent(); + + expect(findLineNumberOld().exists()).toBe(false); + expect(findLineNumberNew().exists()).toBe(true); + }); - vm.$store.state.diffs.coverageFiles = { files: { [name]: { [line]: 0 } } }; + describe('with lineNumber prop', () => { + const TEST_LINE_CODE = 'LC_42'; + const TEST_LINE_NUMBER = 1; - return vm.$nextTick(); - }) - .then(() => { - const coverage = wrapper.find('.line-coverage'); + describe.each` + lineProps | findLineNumber | expectedHref | expectedClickArg + ${{ line_code: TEST_LINE_CODE, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${`#${TEST_LINE_CODE}`} | ${TEST_LINE_CODE} + ${{ line_code: undefined, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${undefined} + ${{ line_code: undefined, left: { line_code: TEST_LINE_CODE }, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${TEST_LINE_CODE} + ${{ line_code: undefined, right: { line_code: TEST_LINE_CODE }, new_line: TEST_LINE_NUMBER }} | ${findLineNumberNew} | ${'#'} | ${TEST_LINE_CODE} + `( + 'with line ($lineProps)', + ({ lineProps, findLineNumber, expectedHref, expectedClickArg }) => { + beforeEach(() => { + jest.spyOn(store, 'dispatch').mockImplementation(); + createComponent({ + line: { ...thisLine, ...lineProps }, + }); + }); - expect(coverage.attributes('title')).toContain('No test coverage'); - expect(coverage.classes('no-coverage')).toBe(true); - }) - .then(done) - .catch(done.fail); + it('renders', () => { + expect(findLineNumber().exists()).toBe(true); + expect(findLineNumber().attributes()).toEqual({ + href: expectedHref, + 'data-linenumber': TEST_LINE_NUMBER.toString(), + }); + }); + + it('on click, dispatches setHighlightedRow', () => { + expect(store.dispatch).toHaveBeenCalledTimes(1); + + findLineNumber().trigger('click'); + + expect(store.dispatch).toHaveBeenCalledWith( + 'diffs/setHighlightedRow', + expectedClickArg, + ); + expect(store.dispatch).toHaveBeenCalledTimes(2); + }); + }, + ); + }); }); - it('for unknown lines', done => { - vm.$nextTick() - .then(() => { - vm.$store.state.diffs.coverageFiles = {}; - - return vm.$nextTick(); - }) - .then(() => { - const coverage = wrapper.find('.line-coverage'); - - expect(coverage.attributes('title')).toBeUndefined(); - expect(coverage.classes('coverage')).toBe(false); - expect(coverage.classes('no-coverage')).toBe(false); - }) - .then(done) - .catch(done.fail); + describe('diff-gutter-avatars', () => { + const TEST_LINE_CODE = 'LC_42'; + const TEST_FILE_HASH = diffFileMockData.file_hash; + const findAvatars = () => wrapper.find(DiffGutterAvatars); + let line; + + beforeEach(() => { + jest.spyOn(store, 'dispatch').mockImplementation(); + + line = { + line_code: TEST_LINE_CODE, + type: 'new', + old_line: null, + new_line: 1, + discussions: [{ ...discussionsMockData }], + discussionsExpanded: true, + text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + meta_data: null, + }; + }); + + describe('with showCommentButton', () => { + it('renders if line has discussions', () => { + createComponent({ line }); + + expect(findAvatars().props()).toEqual({ + discussions: line.discussions, + discussionsExpanded: line.discussionsExpanded, + }); + }); + + it('does notrender if line has no discussions', () => { + line.discussions = []; + createComponent({ line }); + + expect(findAvatars().exists()).toEqual(false); + }); + + it('toggles line discussion', () => { + createComponent({ line }); + + expect(store.dispatch).toHaveBeenCalledTimes(1); + + findAvatars().vm.$emit('toggleLineDiscussions'); + + expect(store.dispatch).toHaveBeenCalledWith('diffs/toggleLineDiscussions', { + lineCode: TEST_LINE_CODE, + fileHash: TEST_FILE_HASH, + expanded: !line.discussionsExpanded, + }); + }); + }); }); }); }); diff --git a/spec/frontend/diffs/components/inline_diff_view_spec.js b/spec/frontend/diffs/components/inline_diff_view_spec.js index 6c37f86658e..39c581e2796 100644 --- a/spec/frontend/diffs/components/inline_diff_view_spec.js +++ b/spec/frontend/diffs/components/inline_diff_view_spec.js @@ -30,8 +30,8 @@ describe('InlineDiffView', () => { it('should have rendered diff lines', () => { const el = component.$el; - expect(el.querySelectorAll('tr.line_holder').length).toEqual(5); - expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(2); + expect(el.querySelectorAll('tr.line_holder').length).toEqual(8); + expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(4); expect(el.querySelectorAll('tr.line_expansion.match').length).toEqual(1); expect(el.textContent.indexOf('Bad dates')).toBeGreaterThan(-1); }); diff --git a/spec/frontend/diffs/components/merge_conflict_warning_spec.js b/spec/frontend/diffs/components/merge_conflict_warning_spec.js new file mode 100644 index 00000000000..2f303f25f66 --- /dev/null +++ b/spec/frontend/diffs/components/merge_conflict_warning_spec.js @@ -0,0 +1,77 @@ +import { shallowMount, mount } from '@vue/test-utils'; +import MergeConflictWarning from '~/diffs/components/merge_conflict_warning.vue'; +import { CENTERED_LIMITED_CONTAINER_CLASSES } from '~/diffs/constants'; + +const propsData = { + limited: true, + mergeable: true, + resolutionPath: 'a-path', +}; +const limitedClasses = CENTERED_LIMITED_CONTAINER_CLASSES.split(' '); + +function findResolveButton(wrapper) { + return wrapper.find('.gl-alert-actions a.gl-button:first-child'); +} +function findLocalMergeButton(wrapper) { + return wrapper.find('.gl-alert-actions button.gl-button:last-child'); +} + +describe('MergeConflictWarning', () => { + let wrapper; + + const createComponent = (props = {}, { full } = { full: false }) => { + const mounter = full ? mount : shallowMount; + + wrapper = mounter(MergeConflictWarning, { + propsData: { ...propsData, ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it.each` + limited | containerClasses + ${true} | ${limitedClasses} + ${false} | ${[]} + `( + 'has the correct container classes when limited is $limited', + ({ limited, containerClasses }) => { + createComponent({ limited }); + + expect(wrapper.classes()).toEqual(containerClasses); + }, + ); + + it.each` + present | resolutionPath + ${false} | ${''} + ${true} | ${'some-path'} + `( + 'toggles the resolve conflicts button based on the provided resolutionPath "$resolutionPath"', + ({ present, resolutionPath }) => { + createComponent({ resolutionPath }, { full: true }); + const resolveButton = findResolveButton(wrapper); + + expect(resolveButton.exists()).toBe(present); + if (present) { + expect(resolveButton.attributes('href')).toBe(resolutionPath); + } + }, + ); + + it.each` + present | mergeable + ${false} | ${false} + ${true} | ${true} + `( + 'toggles the local merge button based on the provided mergeable property "$mergable"', + ({ present, mergeable }) => { + createComponent({ mergeable }, { full: true }); + const localMerge = findLocalMergeButton(wrapper); + + expect(localMerge.exists()).toBe(present); + }, + ); +}); diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js index 2795c68b4ee..78805a1cddc 100644 --- a/spec/frontend/diffs/components/no_changes_spec.js +++ b/spec/frontend/diffs/components/no_changes_spec.js @@ -36,7 +36,7 @@ describe('Diff no changes empty state', () => { }; }); - expect(vm.contains('script')).toBe(false); + expect(vm.find('script').exists()).toBe(false); }); describe('Renders', () => { diff --git a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js index 339352943a9..13c4ce06f18 100644 --- a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js +++ b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js @@ -1,9 +1,12 @@ import Vue from 'vue'; import { shallowMount } from '@vue/test-utils'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import { TEST_HOST } from 'helpers/test_constants'; import { createStore } from '~/mr_notes/stores'; import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue'; import diffFileMockData from '../mock_data/diff_file'; +import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue'; +import discussionsMockData from '../mock_data/diff_discussions'; describe('ParallelDiffTableRow', () => { describe('when one side is empty', () => { @@ -158,4 +161,260 @@ describe('ParallelDiffTableRow', () => { }); }); }); + + describe('Table Cells', () => { + let wrapper; + let store; + let thisLine; + const TEST_USER_ID = 'abc123'; + const TEST_USER = { id: TEST_USER_ID }; + + const createComponent = (props = {}, propsStore = store, data = {}) => { + wrapper = shallowMount(ParallelDiffTableRow, { + store: propsStore, + propsData: { + line: thisLine, + fileHash: diffFileMockData.file_hash, + filePath: diffFileMockData.file_path, + contextLinesPath: 'contextLinesPath', + isHighlighted: false, + ...props, + }, + data() { + return data; + }, + }); + }; + + const setWindowLocation = value => { + Object.defineProperty(window, 'location', { + writable: true, + value, + }); + }; + + beforeEach(() => { + // eslint-disable-next-line prefer-destructuring + thisLine = diffFileMockData.parallel_diff_lines[2]; + store = createStore(); + store.state.notes.userData = TEST_USER; + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const findNewTd = () => wrapper.find({ ref: 'newTd' }); + const findOldTd = () => wrapper.find({ ref: 'oldTd' }); + + describe('td', () => { + it('highlights when isHighlighted true', () => { + store.state.diffs.highlightedRow = thisLine.left.line_code; + createComponent({}, store); + + expect(findNewTd().classes()).toContain('hll'); + expect(findOldTd().classes()).toContain('hll'); + }); + + it('does not highlight when isHighlighted false', () => { + createComponent(); + + expect(findNewTd().classes()).not.toContain('hll'); + expect(findOldTd().classes()).not.toContain('hll'); + }); + }); + + describe('comment button', () => { + const findNoteButton = () => wrapper.find({ ref: 'addDiffNoteButtonLeft' }); + + it.each` + hover | line | userData | query | mergeRefHeadComments | expectation + ${true} | ${{}} | ${TEST_USER} | ${'diff_head=false'} | ${false} | ${true} + ${true} | ${{ line: { left: null } }} | ${TEST_USER} | ${'diff_head=false'} | ${false} | ${false} + ${true} | ${{}} | ${TEST_USER} | ${'diff_head=true'} | ${true} | ${true} + ${true} | ${{}} | ${TEST_USER} | ${'diff_head=true'} | ${false} | ${false} + ${true} | ${{}} | ${null} | ${''} | ${true} | ${false} + ${false} | ${{}} | ${TEST_USER} | ${'diff_head=false'} | ${false} | ${false} + `( + 'exists is $expectation - with userData ($userData) query ($query)', + async ({ hover, line, userData, query, mergeRefHeadComments, expectation }) => { + store.state.notes.userData = userData; + gon.features = { mergeRefHeadComments }; + setWindowLocation({ href: `${TEST_HOST}?${query}` }); + createComponent(line, store); + if (hover) await wrapper.find('.line_holder').trigger('mouseover'); + + expect(findNoteButton().exists()).toBe(expectation); + }, + ); + + it.each` + line | expectation + ${{ ...thisLine, left: { discussions: [] } }} | ${true} + ${{ ...thisLine, left: { type: 'context', discussions: [] } }} | ${false} + ${{ ...thisLine, left: { type: 'old-nonewline', discussions: [] } }} | ${false} + ${{ ...thisLine, left: { discussions: [{}] } }} | ${false} + `('visible is $expectation - line ($line)', async ({ line, expectation }) => { + createComponent({ line }, store, { isLeftHover: true, isCommentButtonRendered: true }); + + expect(findNoteButton().isVisible()).toBe(expectation); + }); + + it.each` + disabled | commentsDisabled + ${'disabled'} | ${true} + ${undefined} | ${false} + `( + 'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled', + ({ disabled, commentsDisabled }) => { + thisLine.left.commentsDisabled = commentsDisabled; + createComponent({ line: { ...thisLine } }, store, { + isLeftHover: true, + isCommentButtonRendered: true, + }); + + expect(findNoteButton().attributes('disabled')).toBe(disabled); + }, + ); + + const symlinkishFileTooltip = + 'Commenting on symbolic links that replace or are replaced by files is currently not supported.'; + const realishFileTooltip = + 'Commenting on files that replace or are replaced by symbolic links is currently not supported.'; + const otherFileTooltip = 'Add a comment to this line'; + const findTooltip = () => wrapper.find({ ref: 'addNoteTooltipLeft' }); + + it.each` + tooltip | commentsDisabled + ${symlinkishFileTooltip} | ${{ wasSymbolic: true }} + ${symlinkishFileTooltip} | ${{ isSymbolic: true }} + ${realishFileTooltip} | ${{ wasReal: true }} + ${realishFileTooltip} | ${{ isReal: true }} + ${otherFileTooltip} | ${false} + `( + 'has the correct tooltip when commentsDisabled=$commentsDisabled', + ({ tooltip, commentsDisabled }) => { + thisLine.left.commentsDisabled = commentsDisabled; + createComponent({ line: { ...thisLine } }, store, { + isLeftHover: true, + isCommentButtonRendered: true, + }); + + expect(findTooltip().attributes('title')).toBe(tooltip); + }, + ); + }); + + describe('line number', () => { + const findLineNumberOld = () => wrapper.find({ ref: 'lineNumberRefOld' }); + const findLineNumberNew = () => wrapper.find({ ref: 'lineNumberRefNew' }); + + it('renders line numbers in correct cells', () => { + createComponent(); + + expect(findLineNumberOld().exists()).toBe(true); + expect(findLineNumberNew().exists()).toBe(true); + }); + + describe('with lineNumber prop', () => { + const TEST_LINE_CODE = 'LC_42'; + const TEST_LINE_NUMBER = 1; + + describe.each` + lineProps | findLineNumber | expectedHref | expectedClickArg + ${{ line_code: TEST_LINE_CODE, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${`#${TEST_LINE_CODE}`} | ${TEST_LINE_CODE} + ${{ line_code: undefined, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${undefined} + `( + 'with line ($lineProps)', + ({ lineProps, findLineNumber, expectedHref, expectedClickArg }) => { + beforeEach(() => { + jest.spyOn(store, 'dispatch').mockImplementation(); + Object.assign(thisLine.left, lineProps); + Object.assign(thisLine.right, lineProps); + createComponent({ + line: { ...thisLine }, + }); + }); + + it('renders', () => { + expect(findLineNumber().exists()).toBe(true); + expect(findLineNumber().attributes()).toEqual({ + href: expectedHref, + 'data-linenumber': TEST_LINE_NUMBER.toString(), + }); + }); + + it('on click, dispatches setHighlightedRow', () => { + expect(store.dispatch).toHaveBeenCalledTimes(1); + + findLineNumber().trigger('click'); + + expect(store.dispatch).toHaveBeenCalledWith( + 'diffs/setHighlightedRow', + expectedClickArg, + ); + expect(store.dispatch).toHaveBeenCalledTimes(2); + }); + }, + ); + }); + }); + + describe('diff-gutter-avatars', () => { + const TEST_LINE_CODE = 'LC_42'; + const TEST_FILE_HASH = diffFileMockData.file_hash; + const findAvatars = () => wrapper.find(DiffGutterAvatars); + let line; + + beforeEach(() => { + jest.spyOn(store, 'dispatch').mockImplementation(); + + line = { + left: { + line_code: TEST_LINE_CODE, + type: 'new', + old_line: null, + new_line: 1, + discussions: [{ ...discussionsMockData }], + discussionsExpanded: true, + text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + meta_data: null, + }, + }; + }); + + describe('with showCommentButton', () => { + it('renders if line has discussions', () => { + createComponent({ line }); + + expect(findAvatars().props()).toEqual({ + discussions: line.left.discussions, + discussionsExpanded: line.left.discussionsExpanded, + }); + }); + + it('does notrender if line has no discussions', () => { + line.left.discussions = []; + createComponent({ line }); + + expect(findAvatars().exists()).toEqual(false); + }); + + it('toggles line discussion', () => { + createComponent({ line }); + + expect(store.dispatch).toHaveBeenCalledTimes(1); + + findAvatars().vm.$emit('toggleLineDiscussions'); + + expect(store.dispatch).toHaveBeenCalledWith('diffs/toggleLineDiscussions', { + lineCode: TEST_LINE_CODE, + fileHash: TEST_FILE_HASH, + expanded: !line.left.discussionsExpanded, + }); + }); + }); + }); + }); }); diff --git a/spec/frontend/diffs/components/parallel_diff_view_spec.js b/spec/frontend/diffs/components/parallel_diff_view_spec.js index cb1a47f60d5..44ed303d0ef 100644 --- a/spec/frontend/diffs/components/parallel_diff_view_spec.js +++ b/spec/frontend/diffs/components/parallel_diff_view_spec.js @@ -1,33 +1,37 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import { createStore } from '~/mr_notes/stores'; import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue'; -import * as constants from '~/diffs/constants'; +import parallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue'; import diffFileMockData from '../mock_data/diff_file'; -describe('ParallelDiffView', () => { - let component; - const getDiffFileMock = () => ({ ...diffFileMockData }); +let wrapper; +const localVue = createLocalVue(); + +localVue.use(Vuex); - beforeEach(() => { - const diffFile = getDiffFileMock(); +function factory() { + const diffFile = { ...diffFileMockData }; + const store = createStore(); - component = createComponentWithStore(Vue.extend(ParallelDiffView), createStore(), { + wrapper = shallowMount(ParallelDiffView, { + localVue, + store, + propsData: { diffFile, diffLines: diffFile.parallel_diff_lines, - }).$mount(); + }, }); +} +describe('ParallelDiffView', () => { afterEach(() => { - component.$destroy(); + wrapper.destroy(); }); - describe('assigned', () => { - describe('diffLines', () => { - it('should normalize lines for empty cells', () => { - expect(component.diffLines[0].left.type).toEqual(constants.EMPTY_CELL_TYPE); - expect(component.diffLines[1].left.type).toEqual(constants.EMPTY_CELL_TYPE); - }); - }); + it('renders diff lines', () => { + factory(); + + expect(wrapper.findAll(parallelDiffTableRow).length).toBe(8); }); }); diff --git a/spec/frontend/diffs/components/settings_dropdown_spec.js b/spec/frontend/diffs/components/settings_dropdown_spec.js index 2e95d79ea49..72330d8efba 100644 --- a/spec/frontend/diffs/components/settings_dropdown_spec.js +++ b/spec/frontend/diffs/components/settings_dropdown_spec.js @@ -7,7 +7,7 @@ import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constant const localVue = createLocalVue(); localVue.use(Vuex); -describe('Diff settiings dropdown component', () => { +describe('Diff settings dropdown component', () => { let vm; let actions; @@ -50,7 +50,7 @@ describe('Diff settiings dropdown component', () => { vm.find('.js-list-view').trigger('click'); - expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), false, undefined); + expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), false); }); it('tree view button dispatches setRenderTreeList with true', () => { @@ -58,53 +58,53 @@ describe('Diff settiings dropdown component', () => { vm.find('.js-tree-view').trigger('click'); - expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), true, undefined); + expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), true); }); - it('sets list button as active when renderTreeList is false', () => { + it('sets list button as selected when renderTreeList is false', () => { createComponent(store => { Object.assign(store.state.diffs, { renderTreeList: false, }); }); - expect(vm.find('.js-list-view').classes('active')).toBe(true); - expect(vm.find('.js-tree-view').classes('active')).toBe(false); + expect(vm.find('.js-list-view').classes('selected')).toBe(true); + expect(vm.find('.js-tree-view').classes('selected')).toBe(false); }); - it('sets tree button as active when renderTreeList is true', () => { + it('sets tree button as selected when renderTreeList is true', () => { createComponent(store => { Object.assign(store.state.diffs, { renderTreeList: true, }); }); - expect(vm.find('.js-list-view').classes('active')).toBe(false); - expect(vm.find('.js-tree-view').classes('active')).toBe(true); + expect(vm.find('.js-list-view').classes('selected')).toBe(false); + expect(vm.find('.js-tree-view').classes('selected')).toBe(true); }); }); describe('compare changes', () => { - it('sets inline button as active', () => { + it('sets inline button as selected', () => { createComponent(store => { Object.assign(store.state.diffs, { diffViewType: INLINE_DIFF_VIEW_TYPE, }); }); - expect(vm.find('.js-inline-diff-button').classes('active')).toBe(true); - expect(vm.find('.js-parallel-diff-button').classes('active')).toBe(false); + expect(vm.find('.js-inline-diff-button').classes('selected')).toBe(true); + expect(vm.find('.js-parallel-diff-button').classes('selected')).toBe(false); }); - it('sets parallel button as active', () => { + it('sets parallel button as selected', () => { createComponent(store => { Object.assign(store.state.diffs, { diffViewType: PARALLEL_DIFF_VIEW_TYPE, }); }); - expect(vm.find('.js-inline-diff-button').classes('active')).toBe(false); - expect(vm.find('.js-parallel-diff-button').classes('active')).toBe(true); + expect(vm.find('.js-inline-diff-button').classes('selected')).toBe(false); + expect(vm.find('.js-parallel-diff-button').classes('selected')).toBe(true); }); it('calls setInlineDiffViewType when clicking inline button', () => { @@ -153,14 +153,10 @@ describe('Diff settiings dropdown component', () => { checkbox.element.checked = true; checkbox.trigger('change'); - expect(actions.setShowWhitespace).toHaveBeenCalledWith( - expect.anything(), - { - showWhitespace: true, - pushState: true, - }, - undefined, - ); + expect(actions.setShowWhitespace).toHaveBeenCalledWith(expect.anything(), { + showWhitespace: true, + pushState: true, + }); }); }); }); diff --git a/spec/frontend/diffs/components/tree_list_spec.js b/spec/frontend/diffs/components/tree_list_spec.js index 14cb2a17aec..cc177a81d88 100644 --- a/spec/frontend/diffs/components/tree_list_spec.js +++ b/spec/frontend/diffs/components/tree_list_spec.js @@ -1,16 +1,26 @@ import Vuex from 'vuex'; -import { mount, createLocalVue } from '@vue/test-utils'; +import { shallowMount, mount, createLocalVue } from '@vue/test-utils'; import TreeList from '~/diffs/components/tree_list.vue'; import createStore from '~/diffs/store/modules'; +import FileTree from '~/vue_shared/components/file_tree.vue'; describe('Diffs tree list component', () => { let wrapper; + let store; const getFileRows = () => wrapper.findAll('.file-row'); const localVue = createLocalVue(); localVue.use(Vuex); - const createComponent = state => { - const store = new Vuex.Store({ + const createComponent = (mountFn = mount) => { + wrapper = mountFn(TreeList, { + store, + localVue, + propsData: { hideFileStats: false }, + }); + }; + + beforeEach(() => { + store = new Vuex.Store({ modules: { diffs: createStore(), }, @@ -23,61 +33,57 @@ describe('Diffs tree list component', () => { addedLines: 10, removedLines: 20, ...store.state.diffs, - ...state, }; + }); - wrapper = mount(TreeList, { - store, - localVue, - propsData: { hideFileStats: false }, + const setupFilesInState = () => { + const treeEntries = { + 'index.js': { + addedLines: 0, + changed: true, + deleted: false, + fileHash: 'test', + key: 'index.js', + name: 'index.js', + path: 'app/index.js', + removedLines: 0, + tempFile: true, + type: 'blob', + parentPath: 'app', + }, + app: { + key: 'app', + path: 'app', + name: 'app', + type: 'tree', + tree: [], + }, + }; + + Object.assign(store.state.diffs, { + treeEntries, + tree: [treeEntries['index.js'], treeEntries.app], }); }; - beforeEach(() => { - localStorage.removeItem('mr_diff_tree_list'); - - createComponent(); - }); - afterEach(() => { wrapper.destroy(); }); - it('renders empty text', () => { - expect(wrapper.text()).toContain('No files found'); + describe('default', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders empty text', () => { + expect(wrapper.text()).toContain('No files found'); + }); }); describe('with files', () => { beforeEach(() => { - const treeEntries = { - 'index.js': { - addedLines: 0, - changed: true, - deleted: false, - fileHash: 'test', - key: 'index.js', - name: 'index.js', - path: 'app/index.js', - removedLines: 0, - tempFile: true, - type: 'blob', - parentPath: 'app', - }, - app: { - key: 'app', - path: 'app', - name: 'app', - type: 'tree', - tree: [], - }, - }; - - createComponent({ - treeEntries, - tree: [treeEntries['index.js'], treeEntries.app], - }); - - return wrapper.vm.$nextTick(); + setupFilesInState(); + createComponent(); }); it('renders tree', () => { @@ -136,4 +142,23 @@ describe('Diffs tree list component', () => { }); }); }); + + describe('with viewedDiffFileIds', () => { + const viewedDiffFileIds = { fileId: '#12345' }; + + beforeEach(() => { + setupFilesInState(); + store.state.diffs.viewedDiffFileIds = viewedDiffFileIds; + }); + + it('passes the viewedDiffFileIds to the FileTree', () => { + createComponent(shallowMount); + + return wrapper.vm.$nextTick().then(() => { + // Have to use $attrs['viewed-files'] because we are passing down an object + // and attributes('') stringifies values (e.g. [object])... + expect(wrapper.find(FileTree).vm.$attrs['viewed-files']).toBe(viewedDiffFileIds); + }); + }); + }); }); diff --git a/spec/frontend/diffs/mock_data/diff_file.js b/spec/frontend/diffs/mock_data/diff_file.js index e4b2fdf6ede..c2a4424ee95 100644 --- a/spec/frontend/diffs/mock_data/diff_file.js +++ b/spec/frontend/diffs/mock_data/diff_file.js @@ -56,8 +56,8 @@ export default { old_line: null, new_line: 1, discussions: [], - text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', - rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + rich_text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', meta_data: null, }, { @@ -66,8 +66,8 @@ export default { old_line: null, new_line: 2, discussions: [], - text: '+<span id="LC2" class="line" lang="plaintext"></span>\n', - rich_text: '+<span id="LC2" class="line" lang="plaintext"></span>\n', + text: '<span id="LC2" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n', meta_data: null, }, { @@ -76,8 +76,8 @@ export default { old_line: 1, new_line: 3, discussions: [], - text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', - rich_text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', meta_data: null, }, { @@ -86,8 +86,8 @@ export default { old_line: 2, new_line: 4, discussions: [], - text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', - rich_text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', + text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n', meta_data: null, }, { @@ -96,8 +96,38 @@ export default { old_line: 3, new_line: 5, discussions: [], - text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', - rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + text: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + rich_text: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_6', + type: 'old', + old_line: 4, + new_line: null, + discussions: [], + text: '<span id="LC6" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC6" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_7', + type: 'new', + old_line: null, + new_line: 5, + discussions: [], + text: '<span id="LC7" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC7" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_9', + type: 'new', + old_line: null, + new_line: 6, + discussions: [], + text: '<span id="LC7" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC7" class="line" lang="plaintext"></span>\n', meta_data: null, }, { @@ -116,43 +146,39 @@ export default { ], parallel_diff_lines: [ { - left: { - type: 'empty-cell', - }, + left: null, right: { line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1', type: 'new', old_line: null, new_line: 1, discussions: [], - text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', rich_text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', meta_data: null, }, }, { - left: { - type: 'empty-cell', - }, + left: null, right: { line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', type: 'new', old_line: null, new_line: 2, discussions: [], - text: '+<span id="LC2" class="line" lang="plaintext"></span>\n', + text: '<span id="LC2" class="line" lang="plaintext"></span>\n', rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n', meta_data: null, }, }, { left: { - line_Code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', type: null, old_line: 1, new_line: 3, discussions: [], - text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', meta_data: null, }, @@ -162,7 +188,7 @@ export default { old_line: 1, new_line: 3, discussions: [], - text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', meta_data: null, }, @@ -174,7 +200,7 @@ export default { old_line: 2, new_line: 4, discussions: [], - text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', + text: '<span id="LC4" class="line" lang="plaintext"></span>\n', rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n', meta_data: null, }, @@ -184,7 +210,7 @@ export default { old_line: 2, new_line: 4, discussions: [], - text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', + text: '<span id="LC4" class="line" lang="plaintext"></span>\n', rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n', meta_data: null, }, @@ -196,7 +222,7 @@ export default { old_line: 3, new_line: 5, discussions: [], - text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + text: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', rich_text: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', meta_data: null, }, @@ -206,13 +232,48 @@ export default { old_line: 3, new_line: 5, discussions: [], - text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + text: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', rich_text: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', meta_data: null, }, }, { left: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_6', + type: 'old', + old_line: 4, + new_line: null, + discussions: [], + text: '<span id="LC6" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC6" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_7', + type: 'new', + old_line: null, + new_line: 5, + discussions: [], + text: '<span id="LC7" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC7" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + }, + { + left: null, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_9', + type: 'new', + old_line: null, + new_line: 6, + discussions: [], + text: '<span id="LC7" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC7" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + }, + { + left: { line_code: null, type: 'match', old_line: null, diff --git a/spec/frontend/diffs/mock_data/diff_metadata.js b/spec/frontend/diffs/mock_data/diff_metadata.js index b73b29e4bc8..cfa0038c06f 100644 --- a/spec/frontend/diffs/mock_data/diff_metadata.js +++ b/spec/frontend/diffs/mock_data/diff_metadata.js @@ -1,6 +1,3 @@ -/* eslint-disable import/prefer-default-export */ -/* https://gitlab.com/gitlab-org/frontend/rfcs/-/issues/20 */ - export const diffMetadata = { real_size: '1', size: 1, diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index 5fef35d6c5b..4f647b0cd41 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -13,7 +13,6 @@ import { } from '~/diffs/constants'; import { setBaseConfig, - fetchDiffFiles, fetchDiffFilesBatch, fetchDiffFilesMeta, fetchCoverageFiles, @@ -101,7 +100,6 @@ describe('DiffsStoreActions', () => { const projectPath = '/root/project'; const dismissEndpoint = '/-/user_callouts'; const showSuggestPopover = false; - const useSingleDiffStyle = false; testAction( setBaseConfig, @@ -113,7 +111,6 @@ describe('DiffsStoreActions', () => { projectPath, dismissEndpoint, showSuggestPopover, - useSingleDiffStyle, }, { endpoint: '', @@ -123,7 +120,6 @@ describe('DiffsStoreActions', () => { projectPath: '', dismissEndpoint: '', showSuggestPopover: true, - useSingleDiffStyle: true, }, [ { @@ -136,7 +132,6 @@ describe('DiffsStoreActions', () => { projectPath, dismissEndpoint, showSuggestPopover, - useSingleDiffStyle, }, }, ], @@ -146,39 +141,6 @@ describe('DiffsStoreActions', () => { }); }); - describe('fetchDiffFiles', () => { - it('should fetch diff files', done => { - const endpoint = '/fetch/diff/files?view=inline&w=1'; - const mock = new MockAdapter(axios); - const res = { diff_files: 1, merge_request_diffs: [] }; - mock.onGet(endpoint).reply(200, res); - - testAction( - fetchDiffFiles, - {}, - { endpoint, diffFiles: [], showWhitespace: false, diffViewType: 'inline' }, - [ - { type: types.SET_LOADING, payload: true }, - { type: types.SET_LOADING, payload: false }, - { type: types.SET_MERGE_REQUEST_DIFFS, payload: res.merge_request_diffs }, - { type: types.SET_DIFF_DATA, payload: res }, - ], - [], - () => { - mock.restore(); - done(); - }, - ); - - fetchDiffFiles({ state: { endpoint }, commit: () => null }) - .then(data => { - expect(data).toEqual(res); - done(); - }) - .catch(done.fail); - }); - }); - describe('fetchDiffFilesBatch', () => { let mock; @@ -223,16 +185,16 @@ describe('DiffsStoreActions', () => { testAction( fetchDiffFilesBatch, {}, - { endpointBatch, useSingleDiffStyle: true, diffViewType: 'inline' }, + { endpointBatch, diffViewType: 'inline' }, [ { type: types.SET_BATCH_LOADING, payload: true }, { type: types.SET_RETRIEVING_BATCHES, payload: true }, { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } }, { type: types.SET_BATCH_LOADING, payload: false }, - { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test' }, + { type: types.VIEW_DIFF_FILE, payload: 'test' }, { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } }, { type: types.SET_BATCH_LOADING, payload: false }, - { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test2' }, + { type: types.VIEW_DIFF_FILE, payload: 'test2' }, { type: types.SET_RETRIEVING_BATCHES, payload: false }, ], [], @@ -253,7 +215,6 @@ describe('DiffsStoreActions', () => { commit: () => {}, state: { endpointBatch: `${endpointBatch}?view=${otherView}`, - useSingleDiffStyle: true, diffViewType: viewStyle, }, }) @@ -283,7 +244,7 @@ describe('DiffsStoreActions', () => { testAction( fetchDiffFilesMeta, {}, - { endpointMetadata }, + { endpointMetadata, diffViewType: 'inline' }, [ { type: types.SET_LOADING, payload: true }, { type: types.SET_LOADING, payload: false }, @@ -299,146 +260,6 @@ describe('DiffsStoreActions', () => { }); }); - describe('when the single diff view feature flag is off', () => { - describe('fetchDiffFiles', () => { - it('should fetch diff files', done => { - const endpoint = '/fetch/diff/files?w=1'; - const mock = new MockAdapter(axios); - const res = { diff_files: 1, merge_request_diffs: [] }; - mock.onGet(endpoint).reply(200, res); - - testAction( - fetchDiffFiles, - {}, - { - endpoint, - diffFiles: [], - showWhitespace: false, - diffViewType: 'inline', - useSingleDiffStyle: false, - currentDiffFileId: null, - }, - [ - { type: types.SET_LOADING, payload: true }, - { type: types.SET_LOADING, payload: false }, - { type: types.SET_MERGE_REQUEST_DIFFS, payload: res.merge_request_diffs }, - { type: types.SET_DIFF_DATA, payload: res }, - ], - [], - () => { - mock.restore(); - done(); - }, - ); - - fetchDiffFiles({ state: { endpoint }, commit: () => null }) - .then(data => { - expect(data).toEqual(res); - done(); - }) - .catch(done.fail); - }); - }); - - 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 res1 = { diff_files: [{ file_hash: 'test' }], pagination: { next_page: 2 } }; - const res2 = { diff_files: [{ file_hash: 'test2' }], pagination: {} }; - mock - .onGet(mergeUrlParams({ per_page: DIFFS_PER_PAGE, w: '1', page: 1 }, endpointBatch)) - .reply(200, res1) - .onGet(mergeUrlParams({ per_page: DIFFS_PER_PAGE, w: '1', page: 2 }, endpointBatch)) - .reply(200, res2); - - testAction( - fetchDiffFilesBatch, - {}, - { endpointBatch, useSingleDiffStyle: false, currentDiffFileId: null }, - [ - { type: types.SET_BATCH_LOADING, payload: true }, - { type: types.SET_RETRIEVING_BATCHES, payload: true }, - { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } }, - { type: types.SET_BATCH_LOADING, payload: false }, - { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test' }, - { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } }, - { type: types.SET_BATCH_LOADING, payload: false }, - { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test2' }, - { type: types.SET_RETRIEVING_BATCHES, payload: false }, - ], - [], - 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', () => { - 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, - {}, - { endpointMetadata, useSingleDiffStyle: false }, - [ - { type: types.SET_LOADING, payload: true }, - { type: types.SET_LOADING, payload: false }, - { type: types.SET_MERGE_REQUEST_DIFFS, payload: diffMetadata.merge_request_diffs }, - { type: types.SET_DIFF_DATA, payload: noFilesData }, - ], - [], - () => { - mock.restore(); - done(); - }, - ); - }); - }); - }); - describe('fetchCoverageFiles', () => { let mock; const endpointCoverage = '/fetch'; @@ -479,7 +300,7 @@ describe('DiffsStoreActions', () => { it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => { testAction(setHighlightedRow, 'ABC_123', {}, [ { type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' }, - { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'ABC' }, + { type: types.VIEW_DIFF_FILE, payload: 'ABC' }, ]); }); }); @@ -589,7 +410,7 @@ describe('DiffsStoreActions', () => { testAction( assignDiscussionsToDiff, [], - { diffFiles: [], useSingleDiffStyle: true }, + { diffFiles: [] }, [], [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }], done, @@ -1083,7 +904,7 @@ describe('DiffsStoreActions', () => { expect(document.location.hash).toBe('#test'); }); - it('commits UPDATE_CURRENT_DIFF_FILE_ID', () => { + it('commits VIEW_DIFF_FILE', () => { const state = { treeEntries: { path: { @@ -1094,7 +915,7 @@ describe('DiffsStoreActions', () => { scrollToFile({ state, commit }, 'path'); - expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, 'test'); + expect(commit).toHaveBeenCalledWith(types.VIEW_DIFF_FILE, 'test'); }); }); @@ -1592,7 +1413,7 @@ describe('DiffsStoreActions', () => { }); describe('setCurrentDiffFileIdFromNote', () => { - it('commits UPDATE_CURRENT_DIFF_FILE_ID', () => { + it('commits VIEW_DIFF_FILE', () => { const commit = jest.fn(); const state = { diffFiles: [{ file_hash: '123' }] }; const rootGetters = { @@ -1602,10 +1423,10 @@ describe('DiffsStoreActions', () => { setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1'); - expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, '123'); + expect(commit).toHaveBeenCalledWith(types.VIEW_DIFF_FILE, '123'); }); - it('does not commit UPDATE_CURRENT_DIFF_FILE_ID when discussion has no diff_file', () => { + it('does not commit VIEW_DIFF_FILE when discussion has no diff_file', () => { const commit = jest.fn(); const state = { diffFiles: [{ file_hash: '123' }] }; const rootGetters = { @@ -1618,7 +1439,7 @@ describe('DiffsStoreActions', () => { expect(commit).not.toHaveBeenCalled(); }); - it('does not commit UPDATE_CURRENT_DIFF_FILE_ID when diff file does not exist', () => { + it('does not commit VIEW_DIFF_FILE when diff file does not exist', () => { const commit = jest.fn(); const state = { diffFiles: [{ file_hash: '123' }] }; const rootGetters = { @@ -1633,12 +1454,12 @@ describe('DiffsStoreActions', () => { }); describe('navigateToDiffFileIndex', () => { - it('commits UPDATE_CURRENT_DIFF_FILE_ID', done => { + it('commits VIEW_DIFF_FILE', done => { testAction( navigateToDiffFileIndex, 0, { diffFiles: [{ file_hash: '123' }] }, - [{ type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: '123' }], + [{ type: types.VIEW_DIFF_FILE, payload: '123' }], [], done, ); diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js index 70047899612..e1d855ae0cf 100644 --- a/spec/frontend/diffs/store/mutations_spec.js +++ b/spec/frontend/diffs/store/mutations_spec.js @@ -11,13 +11,11 @@ describe('DiffsStoreMutations', () => { const state = {}; const endpoint = '/diffs/endpoint'; const projectPath = '/root/project'; - const useSingleDiffStyle = false; - mutations[types.SET_BASE_CONFIG](state, { endpoint, projectPath, useSingleDiffStyle }); + mutations[types.SET_BASE_CONFIG](state, { endpoint, projectPath }); expect(state.endpoint).toEqual(endpoint); expect(state.projectPath).toEqual(projectPath); - expect(state.useSingleDiffStyle).toEqual(useSingleDiffStyle); }); }); @@ -70,12 +68,13 @@ describe('DiffsStoreMutations', () => { }); describe('SET_DIFF_DATA', () => { - it('should set diff data type properly', () => { + it('should not modify the existing state', () => { const state = { diffFiles: [ { - ...diffFileMockData, - parallel_diff_lines: [], + content_sha: diffFileMockData.content_sha, + file_hash: diffFileMockData.file_hash, + highlighted_diff_lines: [], }, ], }; @@ -85,43 +84,7 @@ describe('DiffsStoreMutations', () => { mutations[types.SET_DIFF_DATA](state, diffMock); - const firstLine = state.diffFiles[0].parallel_diff_lines[0]; - - expect(firstLine.right.text).toBeUndefined(); - expect(state.diffFiles.length).toEqual(1); - expect(state.diffFiles[0].renderIt).toEqual(true); - expect(state.diffFiles[0].collapsed).toEqual(false); - }); - - describe('given diffsBatchLoad feature flag is enabled', () => { - beforeEach(() => { - gon.features = { diffsBatchLoad: true }; - }); - - afterEach(() => { - delete gon.features; - }); - - it('should not modify the existing state', () => { - const state = { - diffFiles: [ - { - content_sha: diffFileMockData.content_sha, - file_hash: diffFileMockData.file_hash, - highlighted_diff_lines: [], - }, - ], - }; - const diffMock = { - diff_files: [diffFileMockData], - }; - - mutations[types.SET_DIFF_DATA](state, diffMock); - - // If the batch load is enabled, there shouldn't be any processing - // done on the existing state object, so we shouldn't have this. - expect(state.diffFiles[0].parallel_diff_lines).toBeUndefined(); - }); + expect(state.diffFiles[0].parallel_diff_lines).toBeUndefined(); }); }); @@ -682,6 +645,36 @@ describe('DiffsStoreMutations', () => { expect(state.diffFiles[0].highlighted_diff_lines[0].discussions).toHaveLength(1); expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toBe(1); }); + + it('should add discussion to file', () => { + const state = { + latestDiff: true, + diffFiles: [ + { + file_hash: 'ABC', + discussions: [], + parallel_diff_lines: [], + highlighted_diff_lines: [], + }, + ], + }; + const discussion = { + id: 1, + line_code: 'ABC_1', + diff_discussion: true, + resolvable: true, + diff_file: { + file_hash: state.diffFiles[0].file_hash, + }, + }; + + mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { + discussion, + diffPositionByLineCode: null, + }); + + expect(state.diffFiles[0].discussions.length).toEqual(1); + }); }); describe('REMOVE_LINE_DISCUSSIONS', () => { @@ -774,11 +767,11 @@ describe('DiffsStoreMutations', () => { }); }); - describe('UPDATE_CURRENT_DIFF_FILE_ID', () => { + describe('VIEW_DIFF_FILE', () => { it('updates currentDiffFileId', () => { const state = createState(); - mutations[types.UPDATE_CURRENT_DIFF_FILE_ID](state, 'somefileid'); + mutations[types.VIEW_DIFF_FILE](state, 'somefileid'); expect(state.currentDiffFileId).toBe('somefileid'); }); diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js index 62c82468ea0..39a482c85ae 100644 --- a/spec/frontend/diffs/store/utils_spec.js +++ b/spec/frontend/diffs/store/utils_spec.js @@ -1167,4 +1167,59 @@ describe('DiffsStoreUtils', () => { expect(utils.getDefaultWhitespace(undefined, '0')).toBe(true); }); }); + + describe('isAdded', () => { + it.each` + type | expected + ${'new'} | ${true} + ${'new-nonewline'} | ${true} + ${'old'} | ${false} + `('returns $expected when type is $type', ({ type, expected }) => { + expect(utils.isAdded({ type })).toBe(expected); + }); + }); + + describe('isRemoved', () => { + it.each` + type | expected + ${'old'} | ${true} + ${'old-nonewline'} | ${true} + ${'new'} | ${false} + `('returns $expected when type is $type', ({ type, expected }) => { + expect(utils.isRemoved({ type })).toBe(expected); + }); + }); + + describe('isUnchanged', () => { + it.each` + type | expected + ${null} | ${true} + ${'new'} | ${false} + ${'old'} | ${false} + `('returns $expected when type is $type', ({ type, expected }) => { + expect(utils.isUnchanged({ type })).toBe(expected); + }); + }); + + describe('isMeta', () => { + it.each` + type | expected + ${'match'} | ${true} + ${'new-nonewline'} | ${true} + ${'old-nonewline'} | ${true} + ${'new'} | ${false} + `('returns $expected when type is $type', ({ type, expected }) => { + expect(utils.isMeta({ type })).toBe(expected); + }); + }); + + describe('parallelizeDiffLines', () => { + it('converts inline diff lines to parallel diff lines', () => { + const file = getDiffFileMock(); + + expect(utils.parallelizeDiffLines(file.highlighted_diff_lines)).toEqual( + file.parallel_diff_lines, + ); + }); + }); }); |