summaryrefslogtreecommitdiff
path: root/spec/frontend/diffs/components/diff_file_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/diffs/components/diff_file_spec.js')
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js530
1 files changed, 345 insertions, 185 deletions
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',
+ );
});
});
});