diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 08:27:35 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 08:27:35 +0000 |
commit | 7e9c479f7de77702622631cff2628a9c8dcbc627 (patch) | |
tree | c8f718a08e110ad7e1894510980d2155a6549197 /spec/frontend/diffs | |
parent | e852b0ae16db4052c1c567d9efa4facc81146e88 (diff) | |
download | gitlab-ce-7e9c479f7de77702622631cff2628a9c8dcbc627.tar.gz |
Add latest changes from gitlab-org/gitlab@13-6-stable-eev13.6.0-rc42
Diffstat (limited to 'spec/frontend/diffs')
21 files changed, 887 insertions, 362 deletions
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index 86560470ada..225710eab63 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -697,7 +697,7 @@ describe('diffs/components/app', () => { }); describe('collapsed files', () => { - it('should render the collapsed files warning if there are any collapsed files', () => { + it('should render the collapsed files warning if there are any automatically collapsed files', () => { createComponent({}, ({ state }) => { state.diffs.diffFiles = [{ viewer: { automaticallyCollapsed: true } }]; }); @@ -705,16 +705,14 @@ describe('diffs/components/app', () => { expect(getCollapsedFilesWarning(wrapper).exists()).toBe(true); }); - it('should not render the collapsed files warning if the user has dismissed the alert already', async () => { + it('should not render the collapsed files warning if there are no automatically collapsed files', () => { createComponent({}, ({ state }) => { - state.diffs.diffFiles = [{ viewer: { automaticallyCollapsed: true } }]; + state.diffs.diffFiles = [ + { viewer: { automaticallyCollapsed: false, manuallyCollapsed: true } }, + { viewer: { automaticallyCollapsed: false, manuallyCollapsed: false } }, + ]; }); - expect(getCollapsedFilesWarning(wrapper).exists()).toBe(true); - - wrapper.vm.collapsedWarningDismissed = true; - await wrapper.vm.$nextTick(); - expect(getCollapsedFilesWarning(wrapper).exists()).toBe(false); }); }); diff --git a/spec/frontend/diffs/components/collapsed_files_warning_spec.js b/spec/frontend/diffs/components/collapsed_files_warning_spec.js index 7bbffb7a1cd..75e76d88b6b 100644 --- a/spec/frontend/diffs/components/collapsed_files_warning_spec.js +++ b/spec/frontend/diffs/components/collapsed_files_warning_spec.js @@ -2,7 +2,8 @@ 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'; +import { CENTERED_LIMITED_CONTAINER_CLASSES, EVT_EXPAND_ALL_FILES } from '~/diffs/constants'; +import eventHub from '~/diffs/event_hub'; const propsData = { limited: true, @@ -76,13 +77,13 @@ describe('CollapsedFilesWarning', () => { expect(wrapper.find('[data-testid="root"]').exists()).toBe(false); }); - it('triggers the expandAllFiles action when the alert action button is clicked', () => { + it(`emits the \`${EVT_EXPAND_ALL_FILES}\` event when the alert action button is clicked`, () => { createComponent({}, { full: true }); - jest.spyOn(wrapper.vm.$store, 'dispatch').mockReturnValue(undefined); + jest.spyOn(eventHub, '$emit'); getAlertActionButton().vm.$emit('click'); - expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('diffs/expandAllFiles', undefined); + expect(eventHub.$emit).toHaveBeenCalledWith(EVT_EXPAND_ALL_FILES); }); }); diff --git a/spec/frontend/diffs/components/diff_comment_cell_spec.js b/spec/frontend/diffs/components/diff_comment_cell_spec.js new file mode 100644 index 00000000000..d6b68fc52d7 --- /dev/null +++ b/spec/frontend/diffs/components/diff_comment_cell_spec.js @@ -0,0 +1,43 @@ +import { shallowMount } from '@vue/test-utils'; +import DiffCommentCell from '~/diffs/components/diff_comment_cell.vue'; +import DiffDiscussions from '~/diffs/components/diff_discussions.vue'; +import DiffDiscussionReply from '~/diffs/components/diff_discussion_reply.vue'; + +describe('DiffCommentCell', () => { + const createWrapper = (props = {}) => { + const { renderDiscussion, ...otherProps } = props; + const line = { + discussions: [], + renderDiscussion, + }; + const diffFileHash = 'abc'; + + return shallowMount(DiffCommentCell, { + propsData: { line, diffFileHash, ...otherProps }, + }); + }; + + it('renders discussions if line has discussions', () => { + const wrapper = createWrapper({ renderDiscussion: true }); + + expect(wrapper.find(DiffDiscussions).exists()).toBe(true); + }); + + it('does not render discussions if line has no discussions', () => { + const wrapper = createWrapper(); + + expect(wrapper.find(DiffDiscussions).exists()).toBe(false); + }); + + it('renders discussion reply if line has no draft', () => { + const wrapper = createWrapper(); + + expect(wrapper.find(DiffDiscussionReply).exists()).toBe(true); + }); + + it('does not render discussion reply if line has draft', () => { + const wrapper = createWrapper({ hasDraft: true }); + + expect(wrapper.find(DiffDiscussionReply).exists()).toBe(false); + }); +}); diff --git a/spec/frontend/diffs/components/diff_content_spec.js b/spec/frontend/diffs/components/diff_content_spec.js index 6d0120d888e..e3a6f7f16a9 100644 --- a/spec/frontend/diffs/components/diff_content_spec.js +++ b/spec/frontend/diffs/components/diff_content_spec.js @@ -12,6 +12,8 @@ import DiffDiscussions from '~/diffs/components/diff_discussions.vue'; import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants'; import diffFileMockData from '../mock_data/diff_file'; import { diffViewerModes } from '~/ide/constants'; +import { diffLines } from '~/diffs/store/getters'; +import DiffView from '~/diffs/components/diff_view.vue'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -33,7 +35,7 @@ describe('DiffContent', () => { diffFile: JSON.parse(JSON.stringify(diffFileMockData)), }; - const createComponent = ({ props, state } = {}) => { + const createComponent = ({ props, state, provide } = {}) => { const fakeStore = new Vuex.Store({ getters: { getNoteableData() { @@ -55,6 +57,10 @@ describe('DiffContent', () => { namespaced: true, getters: { draftsForFile: () => () => true, + draftForLine: () => () => true, + shouldRenderDraftRow: () => () => true, + hasParallelDraftLeft: () => () => true, + hasParallelDraftRight: () => () => true, }, }, diffs: { @@ -68,6 +74,7 @@ describe('DiffContent', () => { isInlineView: isInlineViewGetterMock, isParallelView: isParallelViewGetterMock, getCommentFormForDiffFile: getCommentFormForDiffFileGetterMock, + diffLines, }, actions: { saveDiffDiscussion: saveDiffDiscussionMock, @@ -77,6 +84,8 @@ describe('DiffContent', () => { }, }); + const glFeatures = provide ? { ...provide.glFeatures } : {}; + wrapper = shallowMount(DiffContentComponent, { propsData: { ...defaultProps, @@ -84,6 +93,7 @@ describe('DiffContent', () => { }, localVue, store: fakeStore, + provide: { glFeatures }, }); }; @@ -112,6 +122,16 @@ describe('DiffContent', () => { expect(wrapper.find(ParallelDiffView).exists()).toBe(true); }); + it('should render diff view if `unifiedDiffLines` & `unifiedDiffComponents` are true', () => { + isParallelViewGetterMock.mockReturnValue(true); + createComponent({ + props: { diffFile: textDiffFile }, + provide: { glFeatures: { unifiedDiffLines: true, unifiedDiffComponents: true } }, + }); + + expect(wrapper.find(DiffView).exists()).toBe(true); + }); + it('renders rendering more lines loading icon', () => { createComponent({ props: { diffFile: { ...textDiffFile, renderingLines: true } } }); diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js index a04486fc5c7..1b41456f2f5 100644 --- a/spec/frontend/diffs/components/diff_file_header_spec.js +++ b/spec/frontend/diffs/components/diff_file_header_spec.js @@ -1,8 +1,12 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; import { cloneDeep } from 'lodash'; + +import { mockTracking, triggerEvent } from 'helpers/tracking_helper'; + import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import FileIcon from '~/vue_shared/components/file_icon.vue'; import diffDiscussionsMockData from '../mock_data/diff_discussions'; import { truncateSha } from '~/lib/utils/text_utility'; import { diffViewerModes } from '~/ide/constants'; @@ -136,9 +140,25 @@ describe('DiffFileHeader component', () => { }); }); - it('displays a copy to clipboard button', () => { - createComponent(); - expect(wrapper.find(ClipboardButton).exists()).toBe(true); + describe('copy to clipboard', () => { + beforeEach(() => { + createComponent(); + }); + + it('displays a copy to clipboard button', () => { + expect(wrapper.find(ClipboardButton).exists()).toBe(true); + }); + + it('triggers the copy to clipboard tracking event', () => { + const trackingSpy = mockTracking('_category_', wrapper.vm.$el, jest.spyOn); + + triggerEvent('[data-testid="diff-file-copy-clipboard"]'); + + expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_copy_file_button', { + label: 'diff_copy_file_path_button', + property: 'diff_copy_file', + }); + }); }); describe('for submodule', () => { @@ -188,6 +208,14 @@ describe('DiffFileHeader component', () => { }); expect(findFileActions().exists()).toBe(false); }); + + it('renders submodule icon', () => { + createComponent({ + diffFile: submoduleDiffFile, + }); + + expect(wrapper.find(FileIcon).props('submodule')).toBe(true); + }); }); describe('for any file', () => { diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js index a6f0d2bf11d..71e0ffd176f 100644 --- a/spec/frontend/diffs/components/diff_file_spec.js +++ b/spec/frontend/diffs/components/diff_file_spec.js @@ -1,262 +1,422 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import { mockTracking, triggerEvent } from 'helpers/tracking_helper'; -import { createStore } from '~/mr_notes/stores'; -import DiffFileComponent from '~/diffs/components/diff_file.vue'; -import { diffViewerModes, diffViewerErrors } from '~/ide/constants'; +import Vuex from 'vuex'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; + +import createDiffsStore from '~/diffs/store/modules'; +import createNotesStore from '~/notes/stores/modules'; import diffFileMockDataReadable from '../mock_data/diff_file'; import diffFileMockDataUnreadable from '../mock_data/diff_file_unreadable'; -describe('DiffFile', () => { - let vm; - let trackingSpy; +import DiffFileComponent from '~/diffs/components/diff_file.vue'; +import DiffFileHeaderComponent from '~/diffs/components/diff_file_header.vue'; +import DiffContentComponent from '~/diffs/components/diff_content.vue'; - beforeEach(() => { - vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), { - file: JSON.parse(JSON.stringify(diffFileMockDataReadable)), +import eventHub from '~/diffs/event_hub'; +import { + EVT_EXPAND_ALL_FILES, + EVT_PERF_MARK_DIFF_FILES_END, + EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN, +} from '~/diffs/constants'; + +import { diffViewerModes, diffViewerErrors } from '~/ide/constants'; + +function changeViewer(store, index, { automaticallyCollapsed, manuallyCollapsed, name }) { + const file = store.state.diffs.diffFiles[index]; + const newViewer = { + ...file.viewer, + }; + + if (automaticallyCollapsed !== undefined) { + newViewer.automaticallyCollapsed = automaticallyCollapsed; + } + + if (manuallyCollapsed !== undefined) { + newViewer.manuallyCollapsed = manuallyCollapsed; + } + + if (name !== undefined) { + newViewer.name = name; + } + + Object.assign(file, { + viewer: newViewer, + }); +} + +function forceHasDiff({ store, index = 0, inlineLines, parallelLines, readableText }) { + const file = store.state.diffs.diffFiles[index]; + + Object.assign(file, { + highlighted_diff_lines: inlineLines, + parallel_diff_lines: parallelLines, + blob: { + ...file.blob, + readable_text: readableText, + }, + }); +} + +function markFileToBeRendered(store, index = 0) { + const file = store.state.diffs.diffFiles[index]; + + Object.assign(file, { + renderIt: true, + }); +} + +function createComponent({ file, first = false, last = false }) { + const localVue = createLocalVue(); + + localVue.use(Vuex); + + const store = new Vuex.Store({ + ...createNotesStore(), + modules: { + diffs: createDiffsStore(), + }, + }); + + store.state.diffs.diffFiles = [file]; + + const wrapper = shallowMount(DiffFileComponent, { + store, + localVue, + propsData: { + file, canCurrentUserFork: false, viewDiffsFileByFile: false, - }).$mount(); - trackingSpy = mockTracking('_category_', vm.$el, jest.spyOn); + isFirstFile: first, + isLastFile: last, + }, + }); + + return { + localVue, + wrapper, + store, + }; +} + +const findDiffHeader = wrapper => wrapper.find(DiffFileHeaderComponent); +const findDiffContentArea = wrapper => wrapper.find('[data-testid="content-area"]'); +const findLoader = wrapper => wrapper.find('[data-testid="loader-icon"]'); +const findToggleButton = wrapper => wrapper.find('[data-testid="expand-button"]'); + +const toggleFile = wrapper => findDiffHeader(wrapper).vm.$emit('toggleFile'); +const isDisplayNone = element => element.style.display === 'none'; +const getReadableFile = () => JSON.parse(JSON.stringify(diffFileMockDataReadable)); +const getUnreadableFile = () => JSON.parse(JSON.stringify(diffFileMockDataUnreadable)); + +const makeFileAutomaticallyCollapsed = (store, index = 0) => + changeViewer(store, index, { automaticallyCollapsed: true, manuallyCollapsed: null }); +const makeFileOpenByDefault = (store, index = 0) => + changeViewer(store, index, { automaticallyCollapsed: false, manuallyCollapsed: null }); +const makeFileManuallyCollapsed = (store, index = 0) => + changeViewer(store, index, { automaticallyCollapsed: false, manuallyCollapsed: true }); +const changeViewerType = (store, newType, index = 0) => + changeViewer(store, index, { name: diffViewerModes[newType] }); + +describe('DiffFile', () => { + let wrapper; + let store; + + beforeEach(() => { + ({ wrapper, store } = createComponent({ file: getReadableFile() })); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); - const findDiffContent = () => vm.$el.querySelector('.diff-content'); - const isVisible = el => el.style.display !== 'none'; + describe('bus events', () => { + beforeEach(() => { + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + }); + + describe('during mount', () => { + it.each` + first | last | events | file + ${false} | ${false} | ${[]} | ${{ inlineLines: [], parallelLines: [], readableText: true }} + ${true} | ${true} | ${[]} | ${{ inlineLines: [], parallelLines: [], readableText: true }} + ${true} | ${false} | ${[EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN]} | ${false} + ${false} | ${true} | ${[EVT_PERF_MARK_DIFF_FILES_END]} | ${false} + ${true} | ${true} | ${[EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN, EVT_PERF_MARK_DIFF_FILES_END]} | ${false} + `( + 'emits the events $events based on the file and its position ({ first: $first, last: $last }) among all files', + async ({ file, first, last, events }) => { + if (file) { + forceHasDiff({ store, ...file }); + } + + ({ wrapper, store } = createComponent({ + file: store.state.diffs.diffFiles[0], + first, + last, + })); + + await wrapper.vm.$nextTick(); + + expect(eventHub.$emit).toHaveBeenCalledTimes(events.length); + events.forEach(event => { + expect(eventHub.$emit).toHaveBeenCalledWith(event); + }); + }, + ); + }); + + describe('after loading the diff', () => { + it('indicates that it loaded the file', async () => { + forceHasDiff({ store, inlineLines: [], parallelLines: [], readableText: true }); + ({ wrapper, store } = createComponent({ + file: store.state.diffs.diffFiles[0], + first: true, + last: true, + })); + + jest.spyOn(wrapper.vm, 'loadCollapsedDiff').mockResolvedValue(getReadableFile()); + jest.spyOn(window, 'requestIdleCallback').mockImplementation(fn => fn()); + + makeFileAutomaticallyCollapsed(store); + + await wrapper.vm.$nextTick(); // Wait for store updates to flow into the component + + toggleFile(wrapper); + + await wrapper.vm.$nextTick(); // Wait for the load to resolve + await wrapper.vm.$nextTick(); // Wait for the idleCallback + await wrapper.vm.$nextTick(); // Wait for nextTick inside postRender + + expect(eventHub.$emit).toHaveBeenCalledTimes(2); + expect(eventHub.$emit).toHaveBeenCalledWith(EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN); + expect(eventHub.$emit).toHaveBeenCalledWith(EVT_PERF_MARK_DIFF_FILES_END); + }); + }); + }); describe('template', () => { - it('should render component with file header, file content components', done => { - const el = vm.$el; - const { file_hash, file_path } = vm.file; + it('should render component with file header, file content components', async () => { + const el = wrapper.vm.$el; + const { file_hash } = wrapper.vm.file; expect(el.id).toEqual(file_hash); expect(el.classList.contains('diff-file')).toEqual(true); expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0); expect(el.querySelector('.js-file-title')).toBeDefined(); - expect(el.querySelector('[data-testid="diff-file-copy-clipboard"]')).toBeDefined(); - expect(el.querySelector('.file-title-name').innerText.indexOf(file_path)).toBeGreaterThan(-1); + expect(wrapper.find(DiffFileHeaderComponent).exists()).toBe(true); expect(el.querySelector('.js-syntax-highlight')).toBeDefined(); - vm.file.renderIt = true; + markFileToBeRendered(store); + + await wrapper.vm.$nextTick(); - vm.$nextTick() - .then(() => { - expect(el.querySelectorAll('.line_content').length).toBe(8); - expect(el.querySelectorAll('.js-line-expansion-content').length).toBe(1); - triggerEvent('[data-testid="diff-file-copy-clipboard"]'); - }) - .then(done) - .catch(done.fail); + expect(wrapper.find(DiffContentComponent).exists()).toBe(true); }); + }); - it('should track a click event on copy to clip board button', done => { - const el = vm.$el; + describe('collapsing', () => { + describe(`\`${EVT_EXPAND_ALL_FILES}\` event`, () => { + beforeEach(() => { + jest.spyOn(wrapper.vm, 'handleToggle').mockImplementation(() => {}); + }); - expect(el.querySelector('[data-testid="diff-file-copy-clipboard"]')).toBeDefined(); - vm.file.renderIt = true; - vm.$nextTick() - .then(() => { - triggerEvent('[data-testid="diff-file-copy-clipboard"]'); + it('performs the normal file toggle when the file is collapsed', async () => { + makeFileAutomaticallyCollapsed(store); - expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_copy_file_button', { - label: 'diff_copy_file_path_button', - property: 'diff_copy_file', - }); - }) - .then(done) - .catch(done.fail); - }); + await wrapper.vm.$nextTick(); - describe('collapsed', () => { - it('should not have file content', done => { - expect(isVisible(findDiffContent())).toBe(true); - expect(vm.isCollapsed).toEqual(false); - vm.isCollapsed = true; - vm.file.renderIt = true; + eventHub.$emit(EVT_EXPAND_ALL_FILES); - vm.$nextTick(() => { - expect(isVisible(findDiffContent())).toBe(false); - - done(); - }); + expect(wrapper.vm.handleToggle).toHaveBeenCalledTimes(1); }); - it('should have collapsed text and link', done => { - vm.renderIt = true; - vm.isCollapsed = true; + it('does nothing when the file is not collapsed', async () => { + eventHub.$emit(EVT_EXPAND_ALL_FILES); - vm.$nextTick(() => { - expect(vm.$el.innerText).toContain('This diff is collapsed'); - expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); + await wrapper.vm.$nextTick(); - done(); - }); + expect(wrapper.vm.handleToggle).not.toHaveBeenCalled(); }); + }); - it('should have collapsed text and link even before rendered', done => { - vm.renderIt = false; - vm.isCollapsed = true; + describe('user collapsed', () => { + beforeEach(() => { + makeFileManuallyCollapsed(store); + }); - vm.$nextTick(() => { - expect(vm.$el.innerText).toContain('This diff is collapsed'); - expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); + it('should not have any content at all', async () => { + await wrapper.vm.$nextTick(); - done(); + Array.from(findDiffContentArea(wrapper).element.children).forEach(child => { + expect(isDisplayNone(child)).toBe(true); }); }); - it('should be collapsable for unreadable files', done => { - vm.$destroy(); - vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), { - file: JSON.parse(JSON.stringify(diffFileMockDataUnreadable)), - canCurrentUserFork: false, - viewDiffsFileByFile: false, - }).$mount(); + it('should not have the class `has-body` to present the header differently', () => { + expect(wrapper.classes('has-body')).toBe(false); + }); + }); - vm.renderIt = false; - vm.isCollapsed = true; + describe('automatically collapsed', () => { + beforeEach(() => { + makeFileAutomaticallyCollapsed(store); + }); - vm.$nextTick(() => { - expect(vm.$el.innerText).toContain('This diff is collapsed'); - expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); + it('should show the collapsed file warning with expansion button', () => { + expect(findDiffContentArea(wrapper).html()).toContain( + 'Files with large changes are collapsed by default.', + ); + expect(findToggleButton(wrapper).exists()).toBe(true); + }); - done(); - }); + it('should style the component so that it `.has-body` for layout purposes', () => { + expect(wrapper.classes('has-body')).toBe(true); }); + }); - it('should be collapsed for renamed files', done => { - vm.renderIt = true; - vm.isCollapsed = false; - vm.file.highlighted_diff_lines = null; - vm.file.viewer.name = diffViewerModes.renamed; + describe('not collapsed', () => { + beforeEach(() => { + makeFileOpenByDefault(store); + markFileToBeRendered(store); + }); - vm.$nextTick(() => { - expect(vm.$el.innerText).not.toContain('This diff is collapsed'); + it('should have the file content', async () => { + expect(wrapper.find(DiffContentComponent).exists()).toBe(true); + }); - done(); - }); + it('should style the component so that it `.has-body` for layout purposes', () => { + expect(wrapper.classes('has-body')).toBe(true); }); + }); - it('should be collapsed for mode changed files', done => { - vm.renderIt = true; - vm.isCollapsed = false; - vm.file.highlighted_diff_lines = null; - vm.file.viewer.name = diffViewerModes.mode_changed; + describe('toggle', () => { + it('should update store state', async () => { + jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation(() => {}); - vm.$nextTick(() => { - expect(vm.$el.innerText).not.toContain('This diff is collapsed'); + toggleFile(wrapper); - done(); + expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('diffs/setFileCollapsedByUser', { + filePath: wrapper.vm.file.file_path, + collapsed: true, }); }); - it('should have loading icon while loading a collapsed diffs', done => { - vm.isCollapsed = true; - vm.isLoadingCollapsedDiff = true; + describe('fetch collapsed diff', () => { + const prepFile = async (inlineLines, parallelLines, readableText) => { + forceHasDiff({ + store, + inlineLines, + parallelLines, + readableText, + }); + + await wrapper.vm.$nextTick(); - vm.$nextTick(() => { - expect(vm.$el.querySelectorAll('.diff-content.loading').length).toEqual(1); + toggleFile(wrapper); + }; - done(); + beforeEach(() => { + jest.spyOn(wrapper.vm, 'requestDiff').mockImplementation(() => {}); + + makeFileAutomaticallyCollapsed(store); }); - }); - it('should update store state', done => { - jest.spyOn(vm.$store, 'dispatch').mockImplementation(() => {}); + it.each` + inlineLines | parallelLines | readableText + ${[1]} | ${[1]} | ${true} + ${[]} | ${[1]} | ${true} + ${[1]} | ${[]} | ${true} + ${[1]} | ${[1]} | ${false} + ${[]} | ${[]} | ${false} + `( + 'does not make a request to fetch the diff for a diff file like { inline: $inlineLines, parallel: $parallelLines, readableText: $readableText }', + async ({ inlineLines, parallelLines, readableText }) => { + await prepFile(inlineLines, parallelLines, readableText); + + expect(wrapper.vm.requestDiff).not.toHaveBeenCalled(); + }, + ); - vm.isCollapsed = true; + it.each` + inlineLines | parallelLines | readableText + ${[]} | ${[]} | ${true} + `( + 'makes a request to fetch the diff for a diff file like { inline: $inlineLines, parallel: $parallelLines, readableText: $readableText }', + async ({ inlineLines, parallelLines, readableText }) => { + await prepFile(inlineLines, parallelLines, readableText); - vm.$nextTick(() => { - expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/setFileCollapsed', { - filePath: vm.file.file_path, - collapsed: true, - }); + expect(wrapper.vm.requestDiff).toHaveBeenCalled(); + }, + ); + }); + }); - done(); - }); + describe('loading', () => { + it('should have loading icon while loading a collapsed diffs', async () => { + makeFileAutomaticallyCollapsed(store); + wrapper.vm.isLoadingCollapsedDiff = true; + + await wrapper.vm.$nextTick(); + + expect(findLoader(wrapper).exists()).toBe(true); }); + }); - it('updates local state when changing file state', done => { - vm.file.viewer.automaticallyCollapsed = true; + describe('general (other) collapsed', () => { + it('should be expandable for unreadable files', async () => { + ({ wrapper, store } = createComponent({ file: getUnreadableFile() })); + makeFileAutomaticallyCollapsed(store); - vm.$nextTick(() => { - expect(vm.isCollapsed).toBe(true); + await wrapper.vm.$nextTick(); - done(); - }); + expect(findDiffContentArea(wrapper).html()).toContain( + 'Files with large changes are collapsed by default.', + ); + expect(findToggleButton(wrapper).exists()).toBe(true); }); + + it.each` + mode + ${'renamed'} + ${'mode_changed'} + `( + 'should render the DiffContent component for files whose mode is $mode', + async ({ mode }) => { + makeFileOpenByDefault(store); + markFileToBeRendered(store); + changeViewerType(store, mode); + + await wrapper.vm.$nextTick(); + + expect(wrapper.classes('has-body')).toBe(true); + expect(wrapper.find(DiffContentComponent).exists()).toBe(true); + expect(wrapper.find(DiffContentComponent).isVisible()).toBe(true); + }, + ); }); }); describe('too large diff', () => { - it('should have too large warning and blob link', done => { + it('should have too large warning and blob link', async () => { + const file = store.state.diffs.diffFiles[0]; const BLOB_LINK = '/file/view/path'; - vm.file.viewer.error = diffViewerErrors.too_large; - vm.file.viewer.error_message = - 'This source diff could not be displayed because it is too large'; - vm.file.view_path = BLOB_LINK; - vm.file.renderIt = true; - - vm.$nextTick(() => { - expect(vm.$el.innerText).toContain( - 'This source diff could not be displayed because it is too large', - ); - done(); + Object.assign(store.state.diffs.diffFiles[0], { + ...file, + view_path: BLOB_LINK, + renderIt: true, + viewer: { + ...file.viewer, + error: diffViewerErrors.too_large, + error_message: 'This source diff could not be displayed because it is too large', + }, }); - }); - }); - describe('watch collapsed', () => { - it('calls handleLoadCollapsedDiff if collapsed changed & file has no lines', done => { - jest.spyOn(vm, 'handleLoadCollapsedDiff').mockImplementation(() => {}); - - vm.file.highlighted_diff_lines = []; - vm.file.parallel_diff_lines = []; - vm.isCollapsed = true; - - vm.$nextTick() - .then(() => { - vm.isCollapsed = false; - - return vm.$nextTick(); - }) - .then(() => { - expect(vm.handleLoadCollapsedDiff).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); + await wrapper.vm.$nextTick(); - it('does not call handleLoadCollapsedDiff if collapsed changed & file is unreadable', done => { - vm.$destroy(); - vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), { - file: JSON.parse(JSON.stringify(diffFileMockDataUnreadable)), - canCurrentUserFork: false, - viewDiffsFileByFile: false, - }).$mount(); - - jest.spyOn(vm, 'handleLoadCollapsedDiff').mockImplementation(() => {}); - - vm.file.highlighted_diff_lines = []; - vm.file.parallel_diff_lines = undefined; - vm.isCollapsed = true; - - vm.$nextTick() - .then(() => { - vm.isCollapsed = false; - - return vm.$nextTick(); - }) - .then(() => { - expect(vm.handleLoadCollapsedDiff).not.toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); + expect(wrapper.vm.$el.innerText).toContain( + 'This source diff could not be displayed because it is too large', + ); }); }); }); diff --git a/spec/frontend/diffs/components/diff_row_spec.js b/spec/frontend/diffs/components/diff_row_spec.js new file mode 100644 index 00000000000..f9e76cf8107 --- /dev/null +++ b/spec/frontend/diffs/components/diff_row_spec.js @@ -0,0 +1,127 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import diffsModule from '~/diffs/store/modules'; +import DiffRow from '~/diffs/components/diff_row.vue'; + +describe('DiffRow', () => { + const testLines = [ + { + left: { old_line: 1, discussions: [] }, + right: { new_line: 1, discussions: [] }, + hasDiscussionsLeft: true, + hasDiscussionsRight: true, + }, + { + left: {}, + right: {}, + isMatchLineLeft: true, + isMatchLineRight: true, + }, + {}, + { + left: { old_line: 1, discussions: [] }, + right: { new_line: 1, discussions: [] }, + }, + ]; + + const createWrapper = ({ props, state, isLoggedIn = true }) => { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const diffs = diffsModule(); + diffs.state = { ...diffs.state, ...state }; + + const getters = { isLoggedIn: () => isLoggedIn }; + + const store = new Vuex.Store({ + modules: { diffs }, + getters, + }); + + const propsData = { + fileHash: 'abc', + filePath: 'abc', + line: {}, + ...props, + }; + return shallowMount(DiffRow, { propsData, localVue, store }); + }; + + it('isHighlighted returns true if isCommented is true', () => { + const props = { isCommented: true }; + const wrapper = createWrapper({ props }); + expect(wrapper.vm.isHighlighted).toBe(true); + }); + + it('isHighlighted returns true given line.left', () => { + const props = { + line: { + left: { + line_code: 'abc', + }, + }, + }; + const state = { highlightedRow: 'abc' }; + const wrapper = createWrapper({ props, state }); + expect(wrapper.vm.isHighlighted).toBe(true); + }); + + it('isHighlighted returns true given line.right', () => { + const props = { + line: { + right: { + line_code: 'abc', + }, + }, + }; + const state = { highlightedRow: 'abc' }; + const wrapper = createWrapper({ props, state }); + expect(wrapper.vm.isHighlighted).toBe(true); + }); + + it('isHighlighted returns false given line.left', () => { + const props = { + line: { + left: { + line_code: 'abc', + }, + }, + }; + const wrapper = createWrapper({ props }); + expect(wrapper.vm.isHighlighted).toBe(false); + }); + + describe.each` + side + ${'left'} + ${'right'} + `('$side side', ({ side }) => { + it(`renders empty cells if ${side} is unavailable`, () => { + const wrapper = createWrapper({ props: { line: testLines[2] } }); + expect(wrapper.find(`[data-testid="${side}LineNumber"]`).exists()).toBe(false); + expect(wrapper.find(`[data-testid="${side}EmptyCell"]`).exists()).toBe(true); + }); + + it('renders comment button', () => { + const wrapper = createWrapper({ props: { line: testLines[3] } }); + expect(wrapper.find(`[data-testid="${side}CommentButton"]`).exists()).toBe(true); + }); + + it('renders avatars', () => { + const wrapper = createWrapper({ props: { line: testLines[0] } }); + expect(wrapper.find(`[data-testid="${side}Discussions"]`).exists()).toBe(true); + }); + }); + + it('renders left line numbers', () => { + const wrapper = createWrapper({ props: { line: testLines[0] } }); + const lineNumber = testLines[0].left.old_line; + expect(wrapper.find(`[data-linenumber="${lineNumber}"]`).exists()).toBe(true); + }); + + it('renders right line numbers', () => { + const wrapper = createWrapper({ props: { line: testLines[0] } }); + const lineNumber = testLines[0].right.new_line; + expect(wrapper.find(`[data-linenumber="${lineNumber}"]`).exists()).toBe(true); + }); +}); diff --git a/spec/frontend/diffs/components/diff_row_utils_spec.js b/spec/frontend/diffs/components/diff_row_utils_spec.js index 394b6cb1914..c001857fa49 100644 --- a/spec/frontend/diffs/components/diff_row_utils_spec.js +++ b/spec/frontend/diffs/components/diff_row_utils_spec.js @@ -201,3 +201,76 @@ describe('shouldShowCommentButton', () => { }, ); }); + +describe('mapParallel', () => { + it('should assign computed properties to the line object', () => { + const side = { + discussions: [{}], + discussionsExpanded: true, + hasForm: true, + }; + const content = { + diffFile: {}, + hasParallelDraftLeft: () => false, + hasParallelDraftRight: () => false, + draftForLine: () => ({}), + }; + const line = { left: side, right: side }; + const expectation = { + commentRowClasses: '', + draftRowClasses: 'js-temp-notes-holder', + hasDiscussionsLeft: true, + hasDiscussionsRight: true, + isContextLineLeft: false, + isContextLineRight: false, + isMatchLineLeft: false, + isMatchLineRight: false, + isMetaLineLeft: false, + isMetaLineRight: false, + }; + const leftExpectation = { + renderDiscussion: true, + hasDraft: false, + lineDraft: {}, + hasCommentForm: true, + }; + const rightExpectation = { + renderDiscussion: false, + hasDraft: false, + lineDraft: {}, + hasCommentForm: false, + }; + const mapped = utils.mapParallel(content)(line); + + expect(mapped).toMatchObject(expectation); + expect(mapped.left).toMatchObject(leftExpectation); + expect(mapped.right).toMatchObject(rightExpectation); + }); +}); + +describe('mapInline', () => { + it('should assign computed properties to the line object', () => { + const content = { + diffFile: {}, + shouldRenderDraftRow: () => false, + }; + const line = { + discussions: [{}], + discussionsExpanded: true, + hasForm: true, + }; + const expectation = { + commentRowClasses: '', + hasDiscussions: true, + isContextLine: false, + isMatchLine: false, + isMetaLine: false, + renderDiscussion: true, + hasDraft: false, + hasCommentForm: true, + }; + const mapped = utils.mapInline(content)(line); + + expect(mapped).toMatchObject(expectation); + }); +}); diff --git a/spec/frontend/diffs/components/diff_view_spec.js b/spec/frontend/diffs/components/diff_view_spec.js new file mode 100644 index 00000000000..4d90112d8f6 --- /dev/null +++ b/spec/frontend/diffs/components/diff_view_spec.js @@ -0,0 +1,82 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import DiffView from '~/diffs/components/diff_view.vue'; +// import DraftNote from '~/batch_comments/components/draft_note.vue'; +// import DiffRow from '~/diffs/components/diff_row.vue'; +// import DiffCommentCell from '~/diffs/components/diff_comment_cell.vue'; +// import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue'; + +describe('DiffView', () => { + const DiffExpansionCell = { template: `<div/>` }; + const DiffRow = { template: `<div/>` }; + const DiffCommentCell = { template: `<div/>` }; + const DraftNote = { template: `<div/>` }; + const createWrapper = props => { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const batchComments = { + getters: { + shouldRenderDraftRow: () => false, + shouldRenderParallelDraftRow: () => () => true, + draftForLine: () => false, + draftsForFile: () => false, + hasParallelDraftLeft: () => false, + hasParallelDraftRight: () => false, + }, + namespaced: true, + }; + const diffs = { getters: { commitId: () => 'abc123' }, namespaced: true }; + const notes = { + state: { selectedCommentPosition: null, selectedCommentPositionHover: null }, + }; + + const store = new Vuex.Store({ + modules: { diffs, notes, batchComments }, + }); + + const propsData = { + diffFile: {}, + diffLines: [], + ...props, + }; + const stubs = { DiffExpansionCell, DiffRow, DiffCommentCell, DraftNote }; + return shallowMount(DiffView, { propsData, store, localVue, stubs }); + }; + + it('renders a match line', () => { + const wrapper = createWrapper({ diffLines: [{ isMatchLineLeft: true }] }); + expect(wrapper.find(DiffExpansionCell).exists()).toBe(true); + }); + + it.each` + type | side | container | sides | total + ${'parallel'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {} }, right: { lineDraft: {} } }} | ${2} + ${'parallel'} | ${'right'} | ${'.new'} | ${{ left: { lineDraft: {} }, right: { lineDraft: {} } }} | ${2} + ${'inline'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {} } }} | ${1} + ${'inline'} | ${'right'} | ${'.new'} | ${{ right: { lineDraft: {} } }} | ${1} + ${'inline'} | ${'left'} | ${'.old'} | ${{ left: { lineDraft: {} }, right: { lineDraft: {} } }} | ${1} + `( + 'renders a $type comment row with comment cell on $side', + ({ type, container, sides, total }) => { + const wrapper = createWrapper({ + diffLines: [{ renderCommentRow: true, ...sides }], + inline: type === 'inline', + }); + expect(wrapper.findAll(DiffCommentCell).length).toBe(total); + expect( + wrapper + .find(container) + .find(DiffCommentCell) + .exists(), + ).toBe(true); + }, + ); + + it('renders a draft row', () => { + const wrapper = createWrapper({ + diffLines: [{ renderCommentRow: true, left: { lineDraft: { isDraft: true } } }], + }); + expect(wrapper.find(DraftNote).exists()).toBe(true); + }); +}); diff --git a/spec/frontend/diffs/components/inline_diff_expansion_row_spec.js b/spec/frontend/diffs/components/inline_diff_expansion_row_spec.js deleted file mode 100644 index 81e5403d502..00000000000 --- a/spec/frontend/diffs/components/inline_diff_expansion_row_spec.js +++ /dev/null @@ -1,32 +0,0 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import { createStore } from '~/mr_notes/stores'; -import InlineDiffExpansionRow from '~/diffs/components/inline_diff_expansion_row.vue'; -import diffFileMockData from '../mock_data/diff_file'; - -describe('InlineDiffExpansionRow', () => { - const mockData = { ...diffFileMockData }; - const matchLine = mockData.highlighted_diff_lines.pop(); - - const createComponent = (options = {}) => { - const cmp = Vue.extend(InlineDiffExpansionRow); - const defaults = { - fileHash: mockData.file_hash, - contextLinesPath: 'contextLinesPath', - line: matchLine, - isTop: false, - isBottom: false, - }; - const props = { ...defaults, ...options }; - - return createComponentWithStore(cmp, createStore(), props).$mount(); - }; - - describe('template', () => { - it('should render expansion row for match lines', () => { - const vm = createComponent(); - - expect(vm.$el.classList.contains('line_expansion')).toBe(true); - }); - }); -}); 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 c65a39b9083..21e7d7397a0 100644 --- a/spec/frontend/diffs/components/inline_diff_table_row_spec.js +++ b/spec/frontend/diffs/components/inline_diff_table_row_spec.js @@ -4,6 +4,7 @@ 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'; +import { mapInline } from '~/diffs/components/diff_row_utils'; const TEST_USER_ID = 'abc123'; const TEST_USER = { id: TEST_USER_ID }; @@ -11,7 +12,16 @@ const TEST_USER = { id: TEST_USER_ID }; describe('InlineDiffTableRow', () => { let wrapper; let store; - const thisLine = diffFileMockData.highlighted_diff_lines[0]; + const mockDiffContent = { + diffFile: diffFileMockData, + shouldRenderDraftRow: jest.fn(), + hasParallelDraftLeft: jest.fn(), + hasParallelDraftRight: jest.fn(), + draftForLine: jest.fn(), + }; + + const applyMap = mapInline(mockDiffContent); + const thisLine = applyMap(diffFileMockData.highlighted_diff_lines[0]); const createComponent = (props = {}, propsStore = store) => { wrapper = shallowMount(InlineDiffTableRow, { @@ -132,7 +142,7 @@ describe('InlineDiffTableRow', () => { ${true} | ${{ ...thisLine, type: 'old-nonewline', discussions: [] }} | ${false} ${true} | ${{ ...thisLine, discussions: [{}] }} | ${false} `('visible is $expectation - line ($line)', ({ isHover, line, expectation }) => { - createComponent({ line }); + createComponent({ line: applyMap(line) }); wrapper.setData({ isHover }); return wrapper.vm.$nextTick().then(() => { @@ -148,7 +158,7 @@ describe('InlineDiffTableRow', () => { 'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled', ({ disabled, commentsDisabled }) => { createComponent({ - line: { ...thisLine, commentsDisabled }, + line: applyMap({ ...thisLine, commentsDisabled }), }); wrapper.setData({ isHover: true }); @@ -177,7 +187,7 @@ describe('InlineDiffTableRow', () => { 'has the correct tooltip when commentsDisabled=$commentsDisabled', ({ tooltip, commentsDisabled }) => { createComponent({ - line: { ...thisLine, commentsDisabled }, + line: applyMap({ ...thisLine, commentsDisabled }), }); wrapper.setData({ isHover: true }); @@ -216,7 +226,7 @@ describe('InlineDiffTableRow', () => { beforeEach(() => { jest.spyOn(store, 'dispatch').mockImplementation(); createComponent({ - line: { ...thisLine, ...lineProps }, + line: applyMap({ ...thisLine, ...lineProps }), }); }); @@ -268,7 +278,7 @@ describe('InlineDiffTableRow', () => { describe('with showCommentButton', () => { it('renders if line has discussions', () => { - createComponent({ line }); + createComponent({ line: applyMap(line) }); expect(findAvatars().props()).toEqual({ discussions: line.discussions, @@ -278,13 +288,13 @@ describe('InlineDiffTableRow', () => { it('does notrender if line has no discussions', () => { line.discussions = []; - createComponent({ line }); + createComponent({ line: applyMap(line) }); expect(findAvatars().exists()).toEqual(false); }); it('toggles line discussion', () => { - createComponent({ line }); + createComponent({ line: applyMap(line) }); expect(store.dispatch).toHaveBeenCalledTimes(1); diff --git a/spec/frontend/diffs/components/inline_diff_view_spec.js b/spec/frontend/diffs/components/inline_diff_view_spec.js index 39c581e2796..6a1791509fd 100644 --- a/spec/frontend/diffs/components/inline_diff_view_spec.js +++ b/spec/frontend/diffs/components/inline_diff_view_spec.js @@ -1,54 +1,57 @@ -import Vue from 'vue'; import '~/behaviors/markdown/render_gfm'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import { mount } from '@vue/test-utils'; +import { getByText } from '@testing-library/dom'; import { createStore } from '~/mr_notes/stores'; import InlineDiffView from '~/diffs/components/inline_diff_view.vue'; +import { mapInline } from '~/diffs/components/diff_row_utils'; import diffFileMockData from '../mock_data/diff_file'; import discussionsMockData from '../mock_data/diff_discussions'; describe('InlineDiffView', () => { - let component; + let wrapper; const getDiffFileMock = () => ({ ...diffFileMockData }); const getDiscussionsMockData = () => [{ ...discussionsMockData }]; const notesLength = getDiscussionsMockData()[0].notes.length; - beforeEach(done => { - const diffFile = getDiffFileMock(); + const setup = (diffFile, diffLines) => { + const mockDiffContent = { + diffFile, + shouldRenderDraftRow: jest.fn(), + }; const store = createStore(); store.dispatch('diffs/setInlineDiffViewType'); - component = createComponentWithStore(Vue.extend(InlineDiffView), store, { - diffFile, - diffLines: diffFile.highlighted_diff_lines, - }).$mount(); - - Vue.nextTick(done); - }); + wrapper = mount(InlineDiffView, { + store, + propsData: { + diffFile, + diffLines: diffLines.map(mapInline(mockDiffContent)), + }, + }); + }; describe('template', () => { it('should have rendered diff lines', () => { - const el = component.$el; + const diffFile = getDiffFileMock(); + setup(diffFile, diffFile.highlighted_diff_lines); - 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); + expect(wrapper.findAll('tr.line_holder').length).toEqual(8); + expect(wrapper.findAll('tr.line_holder.new').length).toEqual(4); + expect(wrapper.findAll('tr.line_expansion.match').length).toEqual(1); + getByText(wrapper.element, /Bad dates/i); }); - it('should render discussions', done => { - const el = component.$el; - component.diffLines[1].discussions = getDiscussionsMockData(); - component.diffLines[1].discussionsExpanded = true; - - Vue.nextTick(() => { - expect(el.querySelectorAll('.notes_holder').length).toEqual(1); - expect(el.querySelectorAll('.notes_holder .note').length).toEqual(notesLength + 1); - expect(el.innerText.indexOf('comment 5')).toBeGreaterThan(-1); - component.$store.dispatch('setInitialNotes', []); + it('should render discussions', () => { + const diffFile = getDiffFileMock(); + diffFile.highlighted_diff_lines[1].discussions = getDiscussionsMockData(); + diffFile.highlighted_diff_lines[1].discussionsExpanded = true; + setup(diffFile, diffFile.highlighted_diff_lines); - done(); - }); + expect(wrapper.findAll('.notes_holder').length).toEqual(1); + expect(wrapper.findAll('.notes_holder .note').length).toEqual(notesLength + 1); + getByText(wrapper.element, 'comment 5'); + wrapper.vm.$store.dispatch('setInitialNotes', []); }); }); }); diff --git a/spec/frontend/diffs/components/parallel_diff_expansion_row_spec.js b/spec/frontend/diffs/components/parallel_diff_expansion_row_spec.js deleted file mode 100644 index 38112445e8d..00000000000 --- a/spec/frontend/diffs/components/parallel_diff_expansion_row_spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import { createStore } from '~/mr_notes/stores'; -import ParallelDiffExpansionRow from '~/diffs/components/parallel_diff_expansion_row.vue'; -import diffFileMockData from '../mock_data/diff_file'; - -describe('ParallelDiffExpansionRow', () => { - const matchLine = diffFileMockData.highlighted_diff_lines[5]; - - const createComponent = (options = {}) => { - const cmp = Vue.extend(ParallelDiffExpansionRow); - const defaults = { - fileHash: diffFileMockData.file_hash, - contextLinesPath: 'contextLinesPath', - line: matchLine, - isTop: false, - isBottom: false, - }; - const props = { ...defaults, ...options }; - - return createComponentWithStore(cmp, createStore(), props).$mount(); - }; - - describe('template', () => { - it('should render expansion row for match lines', () => { - const vm = createComponent(); - - expect(vm.$el.classList.contains('line_expansion')).toBe(true); - }); - }); -}); 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 13031bd8b66..57eff177261 100644 --- a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js +++ b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js @@ -3,11 +3,22 @@ import { shallowMount } from '@vue/test-utils'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createStore } from '~/mr_notes/stores'; import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue'; +import { mapParallel } from '~/diffs/components/diff_row_utils'; 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', () => { + const mockDiffContent = { + diffFile: diffFileMockData, + shouldRenderDraftRow: jest.fn(), + hasParallelDraftLeft: jest.fn(), + hasParallelDraftRight: jest.fn(), + draftForLine: jest.fn(), + }; + + const applyMap = mapParallel(mockDiffContent); + describe('when one side is empty', () => { let wrapper; let vm; @@ -18,7 +29,7 @@ describe('ParallelDiffTableRow', () => { wrapper = shallowMount(ParallelDiffTableRow, { store: createStore(), propsData: { - line: thisLine, + line: applyMap(thisLine), fileHash: diffFileMockData.file_hash, filePath: diffFileMockData.file_path, contextLinesPath: 'contextLinesPath', @@ -67,7 +78,7 @@ describe('ParallelDiffTableRow', () => { beforeEach(() => { vm = createComponentWithStore(Vue.extend(ParallelDiffTableRow), createStore(), { - line: thisLine, + line: applyMap(thisLine), fileHash: diffFileMockData.file_hash, filePath: diffFileMockData.file_path, contextLinesPath: 'contextLinesPath', @@ -243,7 +254,10 @@ describe('ParallelDiffTableRow', () => { ${{ ...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 }); + createComponent({ line: applyMap(line) }, store, { + isLeftHover: true, + isCommentButtonRendered: true, + }); expect(findNoteButton().isVisible()).toBe(expectation); }); @@ -320,7 +334,7 @@ describe('ParallelDiffTableRow', () => { Object.assign(thisLine.left, lineProps); Object.assign(thisLine.right, lineProps); createComponent({ - line: { ...thisLine }, + line: applyMap({ ...thisLine }), }); }); @@ -357,7 +371,7 @@ describe('ParallelDiffTableRow', () => { beforeEach(() => { jest.spyOn(store, 'dispatch').mockImplementation(); - line = { + line = applyMap({ left: { line_code: TEST_LINE_CODE, type: 'new', @@ -369,7 +383,7 @@ describe('ParallelDiffTableRow', () => { rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', meta_data: null, }, - }; + }); }); describe('with showCommentButton', () => { @@ -384,7 +398,7 @@ describe('ParallelDiffTableRow', () => { it('does notrender if line has no discussions', () => { line.left.discussions = []; - createComponent({ line }); + createComponent({ line: applyMap(line) }); expect(findAvatars().exists()).toEqual(false); }); diff --git a/spec/frontend/diffs/components/tree_list_spec.js b/spec/frontend/diffs/components/tree_list_spec.js index cc177a81d88..c89403e4869 100644 --- a/spec/frontend/diffs/components/tree_list_spec.js +++ b/spec/frontend/diffs/components/tree_list_spec.js @@ -91,12 +91,12 @@ describe('Diffs tree list component', () => { expect( getFileRows() .at(0) - .text(), + .html(), ).toContain('index.js'); expect( getFileRows() .at(1) - .text(), + .html(), ).toContain('app'); }); @@ -138,7 +138,7 @@ describe('Diffs tree list component', () => { wrapper.vm.$store.state.diffs.renderTreeList = false; return wrapper.vm.$nextTick().then(() => { - expect(wrapper.find('.file-row').text()).toContain('index.js'); + expect(wrapper.find('.file-row').html()).toContain('index.js'); }); }); }); diff --git a/spec/frontend/diffs/mock_data/diff_file.js b/spec/frontend/diffs/mock_data/diff_file.js index d3886819a91..cef776c885a 100644 --- a/spec/frontend/diffs/mock_data/diff_file.js +++ b/spec/frontend/diffs/mock_data/diff_file.js @@ -27,6 +27,7 @@ export default { name: 'text', error: null, automaticallyCollapsed: false, + manuallyCollapsed: null, }, added_lines: 2, removed_lines: 0, diff --git a/spec/frontend/diffs/mock_data/diff_file_unreadable.js b/spec/frontend/diffs/mock_data/diff_file_unreadable.js index f6cdca9950a..2a5d694e3b8 100644 --- a/spec/frontend/diffs/mock_data/diff_file_unreadable.js +++ b/spec/frontend/diffs/mock_data/diff_file_unreadable.js @@ -26,6 +26,7 @@ export default { name: 'text', error: null, automaticallyCollapsed: false, + manuallyCollapsed: null, }, added_lines: 0, removed_lines: 0, diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index c3e4ee9c531..0af5ddd9764 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -27,7 +27,6 @@ import { scrollToLineIfNeededInline, scrollToLineIfNeededParallel, loadCollapsedDiff, - expandAllFiles, toggleFileDiscussions, saveDiffDiscussion, setHighlightedRow, @@ -42,7 +41,7 @@ import { fetchFullDiff, toggleFullDiff, switchToFullDiffFromRenamedFile, - setFileCollapsed, + setFileCollapsedByUser, setExpandedDiffLines, setSuggestPopoverDismissed, changeCurrentCommit, @@ -658,23 +657,6 @@ describe('DiffsStoreActions', () => { }); }); - describe('expandAllFiles', () => { - it('should change the collapsed prop from the diffFiles', done => { - testAction( - expandAllFiles, - null, - {}, - [ - { - type: types.EXPAND_ALL_FILES, - }, - ], - [], - done, - ); - }); - }); - describe('toggleFileDiscussions', () => { it('should dispatch collapseDiscussion when all discussions are expanded', () => { const getters = { @@ -1167,7 +1149,11 @@ describe('DiffsStoreActions', () => { file_hash: 'testhash', alternate_viewer: { name: updatedViewerName }, }; - const updatedViewer = { name: updatedViewerName, automaticallyCollapsed: false }; + const updatedViewer = { + name: updatedViewerName, + automaticallyCollapsed: false, + manuallyCollapsed: false, + }; const testData = [{ rich_text: 'test' }, { rich_text: 'file2' }]; let renamedFile; let mock; @@ -1216,13 +1202,18 @@ describe('DiffsStoreActions', () => { }); }); - describe('setFileCollapsed', () => { + describe('setFileUserCollapsed', () => { it('commits SET_FILE_COLLAPSED', done => { testAction( - setFileCollapsed, + setFileCollapsedByUser, { filePath: 'test', collapsed: true }, null, - [{ type: types.SET_FILE_COLLAPSED, payload: { filePath: 'test', collapsed: true } }], + [ + { + type: types.SET_FILE_COLLAPSED, + payload: { filePath: 'test', collapsed: true, trigger: 'manual' }, + }, + ], [], done, ); diff --git a/spec/frontend/diffs/store/getters_spec.js b/spec/frontend/diffs/store/getters_spec.js index 0083f1d8b44..7e936c561fc 100644 --- a/spec/frontend/diffs/store/getters_spec.js +++ b/spec/frontend/diffs/store/getters_spec.js @@ -49,23 +49,53 @@ describe('Diffs Module Getters', () => { }); }); - describe('hasCollapsedFile', () => { - it('returns true when all files are collapsed', () => { - localState.diffFiles = [ - { viewer: { automaticallyCollapsed: true } }, - { viewer: { automaticallyCollapsed: true } }, - ]; + describe('whichCollapsedTypes', () => { + const autoCollapsedFile = { viewer: { automaticallyCollapsed: true, manuallyCollapsed: null } }; + const manuallyCollapsedFile = { + viewer: { automaticallyCollapsed: false, manuallyCollapsed: true }, + }; + const openFile = { viewer: { automaticallyCollapsed: false, manuallyCollapsed: false } }; + + it.each` + description | value | files + ${'all files are automatically collapsed'} | ${true} | ${[{ ...autoCollapsedFile }, { ...autoCollapsedFile }]} + ${'all files are manually collapsed'} | ${true} | ${[{ ...manuallyCollapsedFile }, { ...manuallyCollapsedFile }]} + ${'no files are collapsed in any way'} | ${false} | ${[{ ...openFile }, { ...openFile }]} + ${'some files are collapsed in either way'} | ${true} | ${[{ ...manuallyCollapsedFile }, { ...autoCollapsedFile }, { ...openFile }]} + `('`any` is $value when $description', ({ value, files }) => { + localState.diffFiles = files; + + const getterResult = getters.whichCollapsedTypes(localState); + + expect(getterResult.any).toEqual(value); + }); + + it.each` + description | value | files + ${'all files are automatically collapsed'} | ${true} | ${[{ ...autoCollapsedFile }, { ...autoCollapsedFile }]} + ${'all files are manually collapsed'} | ${false} | ${[{ ...manuallyCollapsedFile }, { ...manuallyCollapsedFile }]} + ${'no files are collapsed in any way'} | ${false} | ${[{ ...openFile }, { ...openFile }]} + ${'some files are collapsed in either way'} | ${true} | ${[{ ...manuallyCollapsedFile }, { ...autoCollapsedFile }, { ...openFile }]} + `('`automatic` is $value when $description', ({ value, files }) => { + localState.diffFiles = files; - expect(getters.hasCollapsedFile(localState)).toEqual(true); + const getterResult = getters.whichCollapsedTypes(localState); + + expect(getterResult.automatic).toEqual(value); }); - it('returns true when at least one file is collapsed', () => { - localState.diffFiles = [ - { viewer: { automaticallyCollapsed: false } }, - { viewer: { automaticallyCollapsed: true } }, - ]; + it.each` + description | value | files + ${'all files are automatically collapsed'} | ${false} | ${[{ ...autoCollapsedFile }, { ...autoCollapsedFile }]} + ${'all files are manually collapsed'} | ${true} | ${[{ ...manuallyCollapsedFile }, { ...manuallyCollapsedFile }]} + ${'no files are collapsed in any way'} | ${false} | ${[{ ...openFile }, { ...openFile }]} + ${'some files are collapsed in either way'} | ${true} | ${[{ ...manuallyCollapsedFile }, { ...autoCollapsedFile }, { ...openFile }]} + `('`manual` is $value when $description', ({ value, files }) => { + localState.diffFiles = files; + + const getterResult = getters.whichCollapsedTypes(localState); - expect(getters.hasCollapsedFile(localState)).toEqual(true); + expect(getterResult.manual).toEqual(value); }); }); diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js index a84ad63c695..c0645faf89e 100644 --- a/spec/frontend/diffs/store/mutations_spec.js +++ b/spec/frontend/diffs/store/mutations_spec.js @@ -126,21 +126,6 @@ describe('DiffsStoreMutations', () => { }); }); - describe('EXPAND_ALL_FILES', () => { - it('should change the collapsed prop from diffFiles', () => { - const diffFile = { - viewer: { - automaticallyCollapsed: true, - }, - }; - const state = { expandAllFiles: true, diffFiles: [diffFile] }; - - mutations[types.EXPAND_ALL_FILES](state); - - expect(state.diffFiles[0].viewer.automaticallyCollapsed).toEqual(false); - }); - }); - describe('ADD_CONTEXT_LINES', () => { it('should call utils.addContextLines with proper params', () => { const options = { diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js index 39a482c85ae..866be0abd22 100644 --- a/spec/frontend/diffs/store/utils_spec.js +++ b/spec/frontend/diffs/store/utils_spec.js @@ -1221,5 +1221,26 @@ describe('DiffsStoreUtils', () => { file.parallel_diff_lines, ); }); + + /** + * What's going on here? + * + * The inline version of parallelizeDiffLines simply keeps the difflines + * in the same order they are received as opposed to shuffling them + * to be "side by side". + * + * This keeps the underlying data structure the same which simplifies + * the components, but keeps the changes grouped together as users + * expect when viewing changes inline. + */ + it('converts inline diff lines to inline diff lines with a parallel structure', () => { + const file = getDiffFileMock(); + const files = utils.parallelizeDiffLines(file.highlighted_diff_lines, true); + + expect(files[5].left).toEqual(file.parallel_diff_lines[5].left); + expect(files[5].right).toBeNull(); + expect(files[6].left).toBeNull(); + expect(files[6].right).toEqual(file.parallel_diff_lines[5].right); + }); }); }); |