diff options
Diffstat (limited to 'spec/frontend/diffs/components')
21 files changed, 998 insertions, 355 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); + }); + }); + }); }); |