Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-11 21:09:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-11 21:09:22 +0300
commit1bd9d2d9499d0d28e62254a28fcd3d913a8704af (patch)
treeea9969a5a4c3ac77858be20d69869674bed5ca43 /spec/frontend/diffs
parentd8877c12347443fa02e0ba53ad8d5cd318f6fa28 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/diffs')
-rw-r--r--spec/frontend/diffs/components/__snapshots__/tree_list_spec.js.snap160
-rw-r--r--spec/frontend/diffs/components/app_spec.js39
-rw-r--r--spec/frontend/diffs/components/diff_file_header_spec.js27
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js146
-rw-r--r--spec/frontend/diffs/components/diff_row_utils_spec.js39
-rw-r--r--spec/frontend/diffs/components/tree_list_spec.js103
-rw-r--r--spec/frontend/diffs/store/actions_spec.js124
-rw-r--r--spec/frontend/diffs/store/getters_spec.js32
-rw-r--r--spec/frontend/diffs/store/mutations_spec.js57
-rw-r--r--spec/frontend/diffs/store/utils_spec.js11
10 files changed, 626 insertions, 112 deletions
diff --git a/spec/frontend/diffs/components/__snapshots__/tree_list_spec.js.snap b/spec/frontend/diffs/components/__snapshots__/tree_list_spec.js.snap
new file mode 100644
index 00000000000..605f6335b5c
--- /dev/null
+++ b/spec/frontend/diffs/components/__snapshots__/tree_list_spec.js.snap
@@ -0,0 +1,160 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Diffs tree list component pinned file files in folders pins 1.rb file 1`] = `
+Array [
+ "📁folder/",
+ "──1.rb",
+ "📁folder",
+ "──📁sub-folder",
+ "────nested-1.rb",
+ "────nested-2.rb",
+ "────nested-3.rb",
+ "──2.rb",
+ "──3.rb",
+ "📁folder-single",
+ "──single.rb",
+ "root-first.rb",
+ "root-last.rb",
+]
+`;
+
+exports[`Diffs tree list component pinned file files in folders pins 2.rb file 1`] = `
+Array [
+ "📁folder/",
+ "──2.rb",
+ "📁folder",
+ "──📁sub-folder",
+ "────nested-1.rb",
+ "────nested-2.rb",
+ "────nested-3.rb",
+ "──1.rb",
+ "──3.rb",
+ "📁folder-single",
+ "──single.rb",
+ "root-first.rb",
+ "root-last.rb",
+]
+`;
+
+exports[`Diffs tree list component pinned file files in folders pins 3.rb file 1`] = `
+Array [
+ "📁folder/",
+ "──3.rb",
+ "📁folder",
+ "──📁sub-folder",
+ "────nested-1.rb",
+ "────nested-2.rb",
+ "────nested-3.rb",
+ "──1.rb",
+ "──2.rb",
+ "📁folder-single",
+ "──single.rb",
+ "root-first.rb",
+ "root-last.rb",
+]
+`;
+
+exports[`Diffs tree list component pinned file files in folders pins nested-1.rb file 1`] = `
+Array [
+ "📁folder/sub-folder/",
+ "──nested-1.rb",
+ "📁folder",
+ "──📁sub-folder",
+ "────nested-2.rb",
+ "────nested-3.rb",
+ "──1.rb",
+ "──2.rb",
+ "──3.rb",
+ "📁folder-single",
+ "──single.rb",
+ "root-first.rb",
+ "root-last.rb",
+]
+`;
+
+exports[`Diffs tree list component pinned file files in folders pins nested-2.rb file 1`] = `
+Array [
+ "📁folder/sub-folder/",
+ "──nested-2.rb",
+ "📁folder",
+ "──📁sub-folder",
+ "────nested-1.rb",
+ "────nested-3.rb",
+ "──1.rb",
+ "──2.rb",
+ "──3.rb",
+ "📁folder-single",
+ "──single.rb",
+ "root-first.rb",
+ "root-last.rb",
+]
+`;
+
+exports[`Diffs tree list component pinned file files in folders pins nested-3.rb file 1`] = `
+Array [
+ "📁folder/sub-folder/",
+ "──nested-3.rb",
+ "📁folder",
+ "──📁sub-folder",
+ "────nested-1.rb",
+ "────nested-2.rb",
+ "──1.rb",
+ "──2.rb",
+ "──3.rb",
+ "📁folder-single",
+ "──single.rb",
+ "root-first.rb",
+ "root-last.rb",
+]
+`;
+
+exports[`Diffs tree list component pinned file files in folders pins root-first.rb file 1`] = `
+Array [
+ "root-first.rb",
+ "📁folder",
+ "──📁sub-folder",
+ "────nested-1.rb",
+ "────nested-2.rb",
+ "────nested-3.rb",
+ "──1.rb",
+ "──2.rb",
+ "──3.rb",
+ "📁folder-single",
+ "──single.rb",
+ "root-last.rb",
+]
+`;
+
+exports[`Diffs tree list component pinned file files in folders pins root-last.rb file 1`] = `
+Array [
+ "root-last.rb",
+ "📁folder",
+ "──📁sub-folder",
+ "────nested-1.rb",
+ "────nested-2.rb",
+ "────nested-3.rb",
+ "──1.rb",
+ "──2.rb",
+ "──3.rb",
+ "📁folder-single",
+ "──single.rb",
+ "root-first.rb",
+]
+`;
+
+exports[`Diffs tree list component pinned file files in folders pins single.rb file 1`] = `
+Array [
+ "📁folder-single/",
+ "──single.rb",
+ "📁folder",
+ "──📁sub-folder",
+ "────nested-1.rb",
+ "────nested-2.rb",
+ "────nested-3.rb",
+ "──1.rb",
+ "──2.rb",
+ "──3.rb",
+ "root-first.rb",
+ "root-last.rb",
+]
+`;
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 63d9a2471b6..813db12e83f 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -31,6 +31,9 @@ import * as urlUtils from '~/lib/utils/url_utility';
import * as commonUtils from '~/lib/utils/common_utils';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { stubPerformanceWebAPI } from 'helpers/performance';
+import { getDiffFileMock } from 'jest/diffs/mock_data/diff_file';
+import waitForPromises from 'helpers/wait_for_promises';
+import { diffMetadata } from 'jest/diffs/mock_data/diff_metadata';
import createDiffsStore from '../create_diffs_store';
import diffsMockData from '../mock_data/merge_request_diffs';
@@ -38,6 +41,8 @@ const mergeRequestDiff = { version_index: 1 };
const TEST_ENDPOINT = `${TEST_HOST}/diff/endpoint`;
const COMMIT_URL = `${TEST_HOST}/COMMIT/OLD`;
const UPDATED_COMMIT_URL = `${TEST_HOST}/COMMIT/NEW`;
+const ENDPOINT_BATCH_URL = `${TEST_HOST}/diff/endpointBatch`;
+const ENDPOINT_METADATA_URL = `${TEST_HOST}/diff/endpointMetadata`;
Vue.use(Vuex);
Vue.use(VueApollo);
@@ -77,8 +82,8 @@ describe('diffs/components/app', () => {
store.dispatch('diffs/setBaseConfig', {
endpoint: TEST_ENDPOINT,
- endpointMetadata: `${TEST_HOST}/diff/endpointMetadata`,
- endpointBatch: `${TEST_HOST}/diff/endpointBatch`,
+ endpointMetadata: ENDPOINT_METADATA_URL,
+ endpointBatch: ENDPOINT_BATCH_URL,
endpointDiffForPath: TEST_ENDPOINT,
projectPath: 'namespace/project',
dismissEndpoint: '',
@@ -126,7 +131,7 @@ describe('diffs/components/app', () => {
const fetchResolver = () => {
store.state.diffs.retrievingBatches = false;
store.state.notes.doneFetchingBatchDiscussions = true;
- store.state.notes.discussions = 'test';
+ store.state.notes.discussions = [];
return Promise.resolve({ real_size: 100 });
};
jest.spyOn(window, 'requestIdleCallback').mockImplementation((fn) => fn());
@@ -861,4 +866,32 @@ describe('diffs/components/app', () => {
expect(loadSpy).not.toHaveBeenCalledWith({ file: store.state.diffs.diffFiles[0] });
});
});
+
+ describe('pinned file', () => {
+ const pinnedFileUrl = 'http://localhost.test/pinned-file';
+ let pinnedFile;
+
+ beforeEach(() => {
+ pinnedFile = getDiffFileMock();
+ mock.onGet(pinnedFileUrl).reply(HTTP_STATUS_OK, { diff_files: [pinnedFile] });
+ mock
+ .onGet(new RegExp(ENDPOINT_BATCH_URL))
+ .reply(HTTP_STATUS_OK, { diff_files: [], pagination: {} });
+ mock.onGet(new RegExp(ENDPOINT_METADATA_URL)).reply(HTTP_STATUS_OK, diffMetadata);
+
+ createComponent({ shouldShow: true, pinnedFileUrl });
+ });
+
+ it('fetches and displays pinned file', async () => {
+ await waitForPromises();
+
+ expect(wrapper.findComponent({ name: 'DynamicScroller' }).props('items')[0].file_hash).toBe(
+ pinnedFile.file_hash,
+ );
+ });
+
+ it('shows a spinner during loading', () => {
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js
index d6539a5bffa..c02875963fd 100644
--- a/spec/frontend/diffs/components/diff_file_header_spec.js
+++ b/spec/frontend/diffs/components/diff_file_header_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import { cloneDeep } from 'lodash';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
@@ -20,6 +20,7 @@ import { truncateSha } from '~/lib/utils/text_utility';
import { __, sprintf } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { TEST_HOST } from 'spec/test_constants';
import testAction from '../../__helpers__/vuex_action_helper';
import diffDiscussionsMockData from '../mock_data/diff_discussions';
@@ -73,6 +74,7 @@ describe('DiffFileHeader component', () => {
setFileCollapsedByUser: jest.fn(),
setFileForcedOpen: jest.fn(),
reviewFile: jest.fn(),
+ unpinFile: jest.fn(),
},
},
},
@@ -87,7 +89,7 @@ describe('DiffFileHeader component', () => {
});
const findHeader = () => wrapper.findComponent({ ref: 'header' });
- const findTitleLink = () => wrapper.findComponent({ ref: 'titleWrapper' });
+ const findTitleLink = () => wrapper.findByTestId('file-title');
const findExpandButton = () => wrapper.findComponent({ ref: 'expandDiffToFullFileButton' });
const findFileActions = () => wrapper.find('.file-actions');
const findModeChangedLine = () => wrapper.findComponent({ ref: 'fileMode' });
@@ -105,7 +107,7 @@ describe('DiffFileHeader component', () => {
mockStoreConfig = cloneDeep(defaultMockStoreConfig);
const store = new Vuex.Store({ ...mockStoreConfig, ...options.store });
- wrapper = shallowMount(DiffFileHeader, {
+ wrapper = shallowMountExtended(DiffFileHeader, {
propsData: {
diffFile,
canCurrentUserFork: false,
@@ -711,4 +713,23 @@ describe('DiffFileHeader component', () => {
expect(wrapper.find('[data-testid="comment-files-button"]').exists()).toEqual(true);
});
+
+ describe('pinned file', () => {
+ beforeEach(() => {
+ window.gon.features = { pinnedFile: true };
+ });
+
+ it('has pinned URL search param', () => {
+ createComponent();
+ const url = new URL(TEST_HOST + findTitleLink().attributes('href'));
+ expect(url.searchParams.get('pin')).toBe(diffFile.file_hash);
+ });
+
+ it('can unpin file', () => {
+ createComponent({ props: { addMergeRequestButtons: true, pinned: true } });
+ const unpinButton = wrapper.findComponentByTestId('unpin-button');
+ unpinButton.vm.$emit('click');
+ expect(mockStoreConfig.modules.diffs.actions.unpinFile).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index a9fbf4632ac..444f4102e26 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -29,6 +29,7 @@ import createNotesStore from '~/notes/stores/modules';
import diffsModule from '~/diffs/store/modules';
import { SOMETHING_WENT_WRONG, SAVING_THE_COMMENT_FAILED } from '~/diffs/i18n';
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
+import { SET_PINNED_FILE_HASH } from '~/diffs/store/mutation_types';
import { getDiffFileMock } from '../mock_data/diff_file';
import diffFileMockDataUnreadable from '../mock_data/diff_file_unreadable';
import diffsMockData from '../mock_data/merge_request_diffs';
@@ -90,49 +91,6 @@ function markFileToBeRendered(store, index = 0) {
});
}
-function createComponent({ file, first = false, last = false, options = {}, props = {} }) {
- const diffs = diffsModule();
- diffs.actions = {
- ...diffs.actions,
- prefetchFileNeighbors: prefetchFileNeighborsMock,
- saveDiffDiscussion: saveDiffDiscussionMock,
- };
-
- diffs.getters = {
- ...diffs.getters,
- diffCompareDropdownTargetVersions: () => [],
- diffCompareDropdownSourceVersions: () => [],
- };
-
- const store = new Vuex.Store({
- ...createNotesStore(),
- modules: { diffs },
- });
-
- store.state.diffs = {
- mergeRequestDiff: diffsMockData[0],
- diffFiles: [file],
- };
-
- const wrapper = shallowMountExtended(DiffFileComponent, {
- store,
- propsData: {
- file,
- canCurrentUserFork: false,
- viewDiffsFileByFile: false,
- isFirstFile: first,
- isLastFile: last,
- ...props,
- },
- ...options,
- });
-
- return {
- wrapper,
- store,
- };
-}
-
const findDiffHeader = (wrapper) => wrapper.findComponent(DiffFileHeaderComponent);
const findDiffContentArea = (wrapper) => wrapper.findByTestId('content-area');
const findLoader = (wrapper) => wrapper.findByTestId('loader-icon');
@@ -159,15 +117,58 @@ const triggerSaveDraftNote = (wrapper, note, parent, error) =>
findNoteForm(wrapper).vm.$emit('handleFormUpdateAddToReview', note, false, parent, error);
describe('DiffFile', () => {
- let readableFile;
let wrapper;
let store;
let axiosMock;
+ function createComponent({
+ file = getReadableFile(),
+ first = false,
+ last = false,
+ options = {},
+ props = {},
+ } = {}) {
+ const diffs = diffsModule();
+ diffs.actions = {
+ ...diffs.actions,
+ prefetchFileNeighbors: prefetchFileNeighborsMock,
+ saveDiffDiscussion: saveDiffDiscussionMock,
+ };
+
+ diffs.getters = {
+ ...diffs.getters,
+ diffCompareDropdownTargetVersions: () => [],
+ diffCompareDropdownSourceVersions: () => [],
+ };
+
+ store = new Vuex.Store({
+ ...createNotesStore(),
+ modules: { diffs },
+ });
+
+ store.state.diffs = {
+ ...store.state.diffs,
+ mergeRequestDiff: diffsMockData[0],
+ diffFiles: [file],
+ };
+
+ wrapper = shallowMountExtended(DiffFileComponent, {
+ store,
+ propsData: {
+ file,
+ canCurrentUserFork: false,
+ viewDiffsFileByFile: false,
+ isFirstFile: first,
+ isLastFile: last,
+ ...props,
+ },
+ ...options,
+ });
+ }
+
beforeEach(() => {
- readableFile = getReadableFile();
axiosMock = new MockAdapter(axios);
- ({ wrapper, store } = createComponent({ file: readableFile }));
+ createComponent();
});
afterEach(() => {
@@ -186,7 +187,6 @@ describe('DiffFile', () => {
`('$description', ({ fileByFile }) => {
createComponent({
props: { viewDiffsFileByFile: fileByFile },
- file: readableFile,
});
if (fileByFile) {
@@ -217,11 +217,11 @@ describe('DiffFile', () => {
forceHasDiff({ store, ...file });
}
- ({ wrapper, store } = createComponent({
+ createComponent({
file: store.state.diffs.diffFiles[0],
first,
last,
- }));
+ });
await nextTick();
@@ -233,14 +233,13 @@ describe('DiffFile', () => {
);
it('emits the "first file shown" and "files end" events when in File-by-File mode', async () => {
- ({ wrapper, store } = createComponent({
- file: getReadableFile(),
+ createComponent({
first: false,
last: false,
props: {
viewDiffsFileByFile: true,
},
- }));
+ });
await nextTick();
@@ -253,11 +252,11 @@ describe('DiffFile', () => {
describe('after loading the diff', () => {
it('indicates that it loaded the file', async () => {
forceHasDiff({ store, inlineLines: [], parallelLines: [], readableText: true });
- ({ wrapper, store } = createComponent({
+ 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());
@@ -314,11 +313,11 @@ describe('DiffFile', () => {
`('should be $bool when { userIsLoggedIn: $loggedIn }', ({ loggedIn, bool }) => {
setLoggedIn(loggedIn);
- ({ wrapper } = createComponent({
+ createComponent({
props: {
file: store.state.diffs.diffFiles[0],
},
- }));
+ });
expect(wrapper.vm.showLocalFileReviews).toBe(bool);
});
@@ -556,7 +555,7 @@ describe('DiffFile', () => {
describe('general (other) collapsed', () => {
it('should be expandable for unreadable files', async () => {
- ({ wrapper, store } = createComponent({ file: getUnreadableFile() }));
+ createComponent({ file: getUnreadableFile() });
makeFileAutomaticallyCollapsed(store);
await nextTick();
@@ -622,7 +621,7 @@ describe('DiffFile', () => {
renderIt: true,
};
- ({ wrapper, store } = createComponent({ file }));
+ createComponent({ file });
expect(wrapper.findByTestId('conflictsAlert').exists()).toBe(false);
});
@@ -634,7 +633,7 @@ describe('DiffFile', () => {
renderIt: true,
};
- ({ wrapper, store } = createComponent({ file }));
+ createComponent({ file });
expect(wrapper.findByTestId('conflictsAlert').exists()).toBe(true);
});
@@ -656,9 +655,9 @@ describe('DiffFile', () => {
...extraProps,
};
- ({ wrapper, store } = createComponent({
+ createComponent({
file,
- }));
+ });
expect(wrapper.findByTestId('file-discussions').exists()).toEqual(exists);
},
@@ -676,9 +675,9 @@ describe('DiffFile', () => {
hasCommentForm,
};
- ({ wrapper, store } = createComponent({
+ createComponent({
file,
- }));
+ });
expect(findNoteForm(wrapper).exists()).toEqual(exists);
},
@@ -694,9 +693,9 @@ describe('DiffFile', () => {
discussions,
};
- ({ wrapper, store } = createComponent({
+ createComponent({
file,
- }));
+ });
expect(wrapper.findByTestId('diff-file-discussions').exists()).toEqual(exists);
});
@@ -712,10 +711,10 @@ describe('DiffFile', () => {
const errorCallback = jest.fn();
beforeEach(() => {
- ({ wrapper, store } = createComponent({
+ createComponent({
file,
options: { provide: { glFeatures: { commentOnFiles: true } } },
- }));
+ });
});
it('calls saveDiffDiscussionMock', () => {
@@ -771,10 +770,10 @@ describe('DiffFile', () => {
const errorCallback = jest.fn();
beforeEach(async () => {
- ({ wrapper, store } = createComponent({
+ createComponent({
file,
options: { provide: { glFeatures: { commentOnFiles: true } } },
- }));
+ });
triggerSaveDraftNote(wrapper, note, parentElement, errorCallback);
@@ -791,4 +790,13 @@ describe('DiffFile', () => {
});
});
});
+
+ describe('pinned file', () => {
+ it('passes down pinned prop', async () => {
+ createComponent();
+ store.commit(`diffs/${SET_PINNED_FILE_HASH}`, getReadableFile().file_hash);
+ await nextTick();
+ expect(wrapper.findComponent(DiffFileHeaderComponent).props('pinned')).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 6e9eb433924..bd9592e4f5e 100644
--- a/spec/frontend/diffs/components/diff_row_utils_spec.js
+++ b/spec/frontend/diffs/components/diff_row_utils_spec.js
@@ -6,6 +6,7 @@ import {
NEW_NO_NEW_LINE_TYPE,
EMPTY_CELL_TYPE,
} from '~/diffs/constants';
+import { getDiffFileMock } from 'jest/diffs/mock_data/diff_file';
const LINE_CODE = 'abc123';
@@ -108,15 +109,47 @@ describe('diff_row_utils', () => {
describe('lineHref', () => {
it(`should return #${LINE_CODE}`, () => {
- expect(utils.lineHref({ line_code: LINE_CODE })).toEqual(`#${LINE_CODE}`);
+ expect(utils.lineHref({ line_code: LINE_CODE }, {})).toEqual(`#${LINE_CODE}`);
});
it(`should return '#' if line is undefined`, () => {
- expect(utils.lineHref()).toEqual('#');
+ expect(utils.lineHref()).toEqual('');
});
it(`should return '#' if line_code is undefined`, () => {
- expect(utils.lineHref({})).toEqual('#');
+ expect(utils.lineHref({}, {})).toEqual('');
+ });
+
+ describe('pinned file', () => {
+ beforeEach(() => {
+ window.gon.features = { pinnedFile: true };
+ });
+
+ afterEach(() => {
+ delete window.gon.features;
+ });
+
+ it(`should return pinned file URL`, () => {
+ const diffFile = getDiffFileMock();
+ expect(utils.lineHref({ line_code: LINE_CODE }, { diffFile })).toEqual(
+ `?pin=${diffFile.file_hash}#${LINE_CODE}`,
+ );
+ });
+ });
+ });
+
+ describe('pinnedFileHref', () => {
+ beforeEach(() => {
+ window.gon.features = { pinnedFile: true };
+ });
+
+ afterEach(() => {
+ delete window.gon.features;
+ });
+
+ it(`should return pinned file URL`, () => {
+ const diffFile = getDiffFileMock();
+ expect(utils.pinnedFileHref(diffFile)).toEqual(`?pin=${diffFile.file_hash}`);
});
});
diff --git a/spec/frontend/diffs/components/tree_list_spec.js b/spec/frontend/diffs/components/tree_list_spec.js
index a54cf9b8bff..230839f0ecf 100644
--- a/spec/frontend/diffs/components/tree_list_spec.js
+++ b/spec/frontend/diffs/components/tree_list_spec.js
@@ -7,6 +7,9 @@ import batchComments from '~/batch_comments/stores/modules/batch_comments';
import DiffFileRow from '~/diffs/components//diff_file_row.vue';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { SET_PINNED_FILE_HASH, SET_TREE_DATA, SET_DIFF_FILES } from '~/diffs/store/mutation_types';
+import { generateTreeList } from '~/diffs/utils/tree_worker_utils';
+import { sortTree } from '~/ide/stores/utils';
describe('Diffs tree list component', () => {
let wrapper;
@@ -58,6 +61,14 @@ describe('Diffs tree list component', () => {
const setupFilesInState = () => {
const treeEntries = {
+ app: {
+ key: 'app',
+ path: 'app',
+ name: 'app',
+ type: 'tree',
+ tree: [],
+ opened: true,
+ },
'index.js': {
addedLines: 0,
changed: true,
@@ -71,6 +82,8 @@ describe('Diffs tree list component', () => {
type: 'blob',
parentPath: 'app',
tree: [],
+ file_path: 'app/index.js',
+ file_hash: 'app-index',
},
'test.rb': {
addedLines: 0,
@@ -85,20 +98,39 @@ describe('Diffs tree list component', () => {
type: 'blob',
parentPath: 'app',
tree: [],
+ file_path: 'app/test.rb',
+ file_hash: 'app-test',
},
- app: {
- key: 'app',
- path: 'app',
- name: 'app',
- type: 'tree',
+ LICENSE: {
+ addedLines: 0,
+ changed: true,
+ deleted: false,
+ fileHash: 'LICENSE',
+ key: 'LICENSE',
+ name: 'LICENSE',
+ path: 'LICENSE',
+ removedLines: 0,
+ tempFile: true,
+ type: 'blob',
+ parentPath: '/',
tree: [],
+ file_path: 'LICENSE',
+ file_hash: 'LICENSE',
},
};
Object.assign(store.state.diffs, {
treeEntries,
- tree: [treeEntries['index.js'], treeEntries.app],
+ tree: [
+ treeEntries.LICENSE,
+ {
+ ...treeEntries.app,
+ tree: [treeEntries['index.js'], treeEntries['test.rb']],
+ },
+ ],
});
+
+ return treeEntries;
};
describe('default', () => {
@@ -149,7 +181,7 @@ describe('Diffs tree list component', () => {
});
it('renders tree', () => {
- expect(getScroller().props('items')).toHaveLength(2);
+ expect(getScroller().props('items')).toHaveLength(4);
});
it('hides file stats', () => {
@@ -169,7 +201,7 @@ describe('Diffs tree list component', () => {
store.state.diffs.renderTreeList = false;
await nextTick();
- expect(getScroller().props('items')).toHaveLength(3);
+ expect(getScroller().props('items')).toHaveLength(5);
});
});
@@ -188,4 +220,59 @@ describe('Diffs tree list component', () => {
expect(getFileRow().props('viewedFiles')).toBe(viewedDiffFileIds);
});
});
+
+ describe('pinned file', () => {
+ const filePaths = [
+ ['nested-1.rb', 'folder/sub-folder/'],
+ ['nested-2.rb', 'folder/sub-folder/'],
+ ['nested-3.rb', 'folder/sub-folder/'],
+ ['1.rb', 'folder/'],
+ ['2.rb', 'folder/'],
+ ['3.rb', 'folder/'],
+ ['single.rb', 'folder-single/'],
+ ['root-first.rb'],
+ ['root-last.rb'],
+ ];
+
+ const pinFile = (fileHash) => {
+ store.commit(`diffs/${SET_PINNED_FILE_HASH}`, fileHash);
+ };
+
+ const setupFiles = (diffFiles) => {
+ const { treeEntries, tree } = generateTreeList(diffFiles);
+ store.commit(`diffs/${SET_DIFF_FILES}`, diffFiles);
+ store.commit(`diffs/${SET_TREE_DATA}`, {
+ treeEntries,
+ tree: sortTree(tree),
+ });
+ };
+
+ const createFile = (name, path = '') => ({
+ file_hash: name,
+ path: `${path}${name}`,
+ new_path: `${path}${name}`,
+ file_path: `${path}${name}`,
+ });
+
+ beforeEach(() => {
+ createComponent();
+ setupFiles(filePaths.map(([name, path]) => createFile(name, path)));
+ });
+
+ describe('files in folders', () => {
+ it.each(filePaths.map((path) => path[0]))('pins %s file', async (pinnedFile) => {
+ pinFile(pinnedFile);
+ await nextTick();
+ const items = getScroller().props('items');
+ expect(
+ items.map(
+ (item) =>
+ `${'─'.repeat(item.level * 2)}${item.type === 'tree' ? '📁' : ''}${
+ item.name || item.path
+ }`,
+ ),
+ ).toMatchSnapshot();
+ });
+ });
+ });
});
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index be3b30e8e7a..c68f3c5991e 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -11,7 +11,7 @@ import {
PARALLEL_DIFF_VIEW_TYPE,
EVT_MR_PREPARED,
} from '~/diffs/constants';
-import { LOAD_SINGLE_DIFF_FAILED, BUILDING_YOUR_MR, SOMETHING_WENT_WRONG } from '~/diffs/i18n';
+import { BUILDING_YOUR_MR, SOMETHING_WENT_WRONG } from '~/diffs/i18n';
import * as diffActions from '~/diffs/store/actions';
import * as types from '~/diffs/store/mutation_types';
import * as utils from '~/diffs/store/utils';
@@ -28,6 +28,8 @@ import {
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '~/notes/event_hub';
import diffsEventHub from '~/diffs/event_hub';
+import { handleLocationHash, historyPushState, scrollToElement } from '~/lib/utils/common_utils';
+import setWindowLocation from 'helpers/set_window_location_helper';
import { diffMetadata } from '../mock_data/diff_metadata';
jest.mock('~/alert');
@@ -37,6 +39,8 @@ jest.mock('~/lib/utils/secret_detection', () => ({
containsSensitiveToken: jest.requireActual('~/lib/utils/secret_detection').containsSensitiveToken,
}));
+const endpointDiffForPath = '/diffs/set/endpoint/path';
+
describe('DiffsStoreActions', () => {
let mock;
@@ -78,7 +82,6 @@ describe('DiffsStoreActions', () => {
const endpoint = '/diffs/set/endpoint';
const endpointMetadata = '/diffs/set/endpoint/metadata';
const endpointBatch = '/diffs/set/endpoint/batch';
- const endpointDiffForPath = '/diffs/set/endpoint/path';
const endpointCoverage = '/diffs/set/coverage_reports';
const projectPath = '/root/project';
const dismissEndpoint = '/-/user_callouts';
@@ -181,7 +184,6 @@ describe('DiffsStoreActions', () => {
w: '1',
view: 'inline',
};
- const endpointDiffForPath = '/diffs/set/endpoint/path';
const diffForPath = mergeUrlParams(defaultParams, endpointDiffForPath);
const treeEntry = {
fileHash: 'e334a2a10f036c00151a04cea7938a5d4213a818',
@@ -350,7 +352,6 @@ describe('DiffsStoreActions', () => {
w: '1',
view: 'inline',
};
- const endpointDiffForPath = '/diffs/set/endpoint/path';
const diffForPath = mergeUrlParams(defaultParams, endpointDiffForPath);
const treeEntry = {
fileHash: 'e334a2a10f036c00151a04cea7938a5d4213a818',
@@ -490,8 +491,8 @@ describe('DiffsStoreActions', () => {
describe('fetchDiffFilesBatch', () => {
it('should fetch batch diff files', () => {
const endpointBatch = '/fetch/diffs_batch';
- const res1 = { diff_files: [{ file_hash: 'test' }], pagination: { total_pages: 7 } };
- const res2 = { diff_files: [{ file_hash: 'test2' }], pagination: { total_pages: 7 } };
+ const res1 = { diff_files: [{ file_hash: 'test' }], pagination: { total_pages: 2 } };
+ const res2 = { diff_files: [{ file_hash: 'test2' }], pagination: { total_pages: 2 } };
mock
.onGet(
mergeUrlParams(
@@ -520,7 +521,7 @@ describe('DiffsStoreActions', () => {
return testAction(
diffActions.fetchDiffFilesBatch,
- {},
+ undefined,
{ endpointBatch, diffViewType: 'inline', diffFiles: [], perPage: 5 },
[
{ type: types.SET_BATCH_LOADING_STATE, payload: 'loading' },
@@ -532,7 +533,6 @@ describe('DiffsStoreActions', () => {
{ type: types.SET_BATCH_LOADING_STATE, payload: 'loaded' },
{ type: types.SET_CURRENT_DIFF_FILE, payload: 'test2' },
{ type: types.SET_RETRIEVING_BATCHES, payload: false },
- { type: types.SET_BATCH_LOADING_STATE, payload: 'error' },
],
[],
);
@@ -690,7 +690,7 @@ describe('DiffsStoreActions', () => {
describe('setHighlightedRow', () => {
it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => {
- return testAction(diffActions.setHighlightedRow, 'ABC_123', {}, [
+ return testAction(diffActions.setHighlightedRow, { lineCode: 'ABC_123' }, {}, [
{ type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' },
{ type: types.SET_CURRENT_DIFF_FILE, payload: 'ABC' },
]);
@@ -1310,14 +1310,17 @@ describe('DiffsStoreActions', () => {
diffActions.goToFile({ state, dispatch, getters, commit }, file);
expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, fileHash);
- expect(dispatch).toHaveBeenCalledTimes(0);
+ expect(dispatch).not.toHaveBeenCalledWith('fetchFileByFile');
});
describe('when the tree entry has not been loaded', () => {
it('updates location hash', () => {
diffActions.goToFile({ state, commit, getters, dispatch }, file);
- expect(document.location.hash).toBe('#test');
+ expect(historyPushState).toHaveBeenCalledWith(new URL(`${TEST_HOST}#test`), {
+ skipScrolling: true,
+ });
+ expect(scrollToElement).toHaveBeenCalledWith('.diff-files-holder', { duration: 0 });
});
it('loads the file and then scrolls to it', async () => {
@@ -1333,21 +1336,12 @@ describe('DiffsStoreActions', () => {
expect(commonUtils.scrollToElement).toHaveBeenCalledWith('.diff-files-holder', {
duration: 0,
});
- expect(dispatch).toHaveBeenCalledTimes(1);
+ expect(dispatch).toHaveBeenCalledWith('fetchFileByFile');
});
- it('shows an alert when there was an error fetching the file', async () => {
- dispatch = jest.fn().mockRejectedValue();
-
+ it('unpins the file', () => {
diffActions.goToFile({ state, commit, getters, dispatch }, file);
-
- // Wait for the fetchFileByFile dispatch to return, to trigger the catch
- await waitForPromises();
-
- expect(createAlert).toHaveBeenCalledTimes(1);
- expect(createAlert).toHaveBeenCalledWith({
- message: expect.stringMatching(LOAD_SINGLE_DIFF_FAILED),
- });
+ expect(dispatch).toHaveBeenCalledWith('unpinFile');
});
});
});
@@ -1969,7 +1963,7 @@ describe('DiffsStoreActions', () => {
0,
{ flatBlobsList: [{ fileHash: '123' }] },
[{ type: types.SET_CURRENT_DIFF_FILE, payload: '123' }],
- [],
+ [{ type: 'unpinFile' }],
);
});
@@ -1979,7 +1973,7 @@ describe('DiffsStoreActions', () => {
0,
{ viewDiffsFileByFile: true, flatBlobsList: [{ fileHash: '123' }] },
[{ type: types.SET_CURRENT_DIFF_FILE, payload: '123' }],
- [{ type: 'fetchFileByFile' }],
+ [{ type: 'unpinFile' }, { type: 'fetchFileByFile' }],
);
});
});
@@ -2120,4 +2114,84 @@ describe('DiffsStoreActions', () => {
);
});
});
+
+ describe('fetchPinnedFile', () => {
+ it('fetches pinned file', async () => {
+ const pinnedFileHref = `${TEST_HOST}/pinned-file`;
+ const pinnedFile = getDiffFileMock();
+ const diffFiles = [pinnedFile];
+ const hubSpy = jest.spyOn(diffsEventHub, '$emit');
+ mock.onGet(new RegExp(pinnedFileHref)).reply(HTTP_STATUS_OK, { diff_files: diffFiles });
+
+ await testAction(
+ diffActions.fetchPinnedFile,
+ pinnedFileHref,
+ {},
+ [
+ { type: types.SET_BATCH_LOADING_STATE, payload: 'loading' },
+ { type: types.SET_RETRIEVING_BATCHES, payload: true },
+ {
+ type: types.SET_DIFF_DATA_BATCH,
+ payload: { diff_files: diffFiles, updatePosition: false },
+ },
+ { type: types.SET_PINNED_FILE_HASH, payload: pinnedFile.file_hash },
+ { type: types.SET_CURRENT_DIFF_FILE, payload: pinnedFile.file_hash },
+ { type: types.SET_BATCH_LOADING_STATE, payload: 'loaded' },
+ { type: types.SET_RETRIEVING_BATCHES, payload: false },
+ ],
+ [],
+ );
+
+ jest.runAllTimers();
+ expect(hubSpy).toHaveBeenCalledWith('diffFilesModified');
+ expect(handleLocationHash).toHaveBeenCalled();
+ });
+
+ it('handles load error', async () => {
+ const pinnedFileHref = `${TEST_HOST}/pinned-file`;
+ const hubSpy = jest.spyOn(diffsEventHub, '$emit');
+ mock.onGet(new RegExp(pinnedFileHref)).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+
+ try {
+ await testAction(
+ diffActions.fetchPinnedFile,
+ pinnedFileHref,
+ {},
+ [
+ { type: types.SET_BATCH_LOADING_STATE, payload: 'loading' },
+ { type: types.SET_RETRIEVING_BATCHES, payload: true },
+ { type: types.SET_BATCH_LOADING_STATE, payload: 'error' },
+ { type: types.SET_RETRIEVING_BATCHES, payload: false },
+ ],
+ [],
+ );
+ } catch (error) {
+ expect(error.response.status).toBe(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ }
+
+ jest.runAllTimers();
+ expect(hubSpy).not.toHaveBeenCalledWith('diffFilesModified');
+ expect(handleLocationHash).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('unpinFile', () => {
+ it('unpins pinned file', () => {
+ const pinnedFile = getDiffFileMock();
+ setWindowLocation(`${TEST_HOST}/?pin=${pinnedFile.file_hash}#${pinnedFile.file_hash}_10_10`);
+ testAction(
+ diffActions.unpinFile,
+ undefined,
+ { pinnedFile },
+ [{ type: types.SET_PINNED_FILE_HASH, payload: null }],
+ [],
+ );
+ expect(window.location.hash).toBe('');
+ expect(window.location.search).toBe('');
+ });
+
+ it('does nothing when no pinned file present', () => {
+ testAction(diffActions.unpinFile, undefined, {}, [], []);
+ });
+ });
});
diff --git a/spec/frontend/diffs/store/getters_spec.js b/spec/frontend/diffs/store/getters_spec.js
index 8097f0976f6..cb0f40534fe 100644
--- a/spec/frontend/diffs/store/getters_spec.js
+++ b/spec/frontend/diffs/store/getters_spec.js
@@ -1,6 +1,7 @@
import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
import * as getters from '~/diffs/store/getters';
import state from '~/diffs/store/modules/diff_state';
+import { getDiffFileMock } from 'jest/diffs/mock_data/diff_file';
import discussion from '../mock_data/diff_discussions';
describe('Diffs Module Getters', () => {
@@ -495,4 +496,35 @@ describe('Diffs Module Getters', () => {
},
);
});
+
+ describe('diffFiles', () => {
+ it('proxies diffFiles state', () => {
+ const diffFiles = [getDiffFileMock()];
+ expect(getters.diffFiles({ diffFiles }, {})).toBe(diffFiles);
+ });
+
+ it('pins the file', () => {
+ const pinnedFile = getDiffFileMock();
+ const regularFile = getDiffFileMock();
+ const diffFiles = [regularFile, pinnedFile];
+ expect(getters.diffFiles({ diffFiles }, { pinnedFile })).toStrictEqual([
+ pinnedFile,
+ regularFile,
+ ]);
+ });
+ });
+
+ describe('pinnedFile', () => {
+ it('returns pinnedFile', () => {
+ const pinnedFile = getDiffFileMock();
+ const diffFiles = [pinnedFile];
+ expect(getters.pinnedFile({ diffFiles, pinnedFileHash: pinnedFile.file_hash }, {})).toBe(
+ pinnedFile,
+ );
+ });
+
+ it('returns null if no pinned file is set', () => {
+ expect(getters.pinnedFile({}, {})).toBe(null);
+ });
+ });
});
diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js
index a5be41aa69f..8d52cd39542 100644
--- a/spec/frontend/diffs/store/mutations_spec.js
+++ b/spec/frontend/diffs/store/mutations_spec.js
@@ -92,7 +92,7 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('SET_DIFF_DATA_BATCH_DATA', () => {
+ describe('SET_DIFF_DATA_BATCH', () => {
it('should set diff data batch type properly', () => {
const mockFile = getDiffFileMock();
const state = {
@@ -108,6 +108,39 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[0].collapsed).toEqual(false);
expect(state.treeEntries[mockFile.file_path].diffLoaded).toBe(true);
});
+
+ it('should update diff position by default', () => {
+ const mockFile = getDiffFileMock();
+ const state = {
+ diffFiles: [mockFile, { ...mockFile, file_hash: 'foo', file_path: 'foo' }],
+ treeEntries: { [mockFile.file_path]: { fileHash: mockFile.file_hash } },
+ };
+ const diffMock = {
+ diff_files: [mockFile],
+ };
+
+ mutations[types.SET_DIFF_DATA_BATCH](state, diffMock);
+
+ expect(state.diffFiles[1].file_hash).toBe(mockFile.file_hash);
+ expect(state.treeEntries[mockFile.file_path].diffLoaded).toBe(true);
+ });
+
+ it('should not update diff position', () => {
+ const mockFile = getDiffFileMock();
+ const state = {
+ diffFiles: [mockFile, { ...mockFile, file_hash: 'foo', file_path: 'foo' }],
+ treeEntries: { [mockFile.file_path]: { fileHash: mockFile.file_hash } },
+ };
+ const diffMock = {
+ diff_files: [mockFile],
+ updatePosition: false,
+ };
+
+ mutations[types.SET_DIFF_DATA_BATCH](state, diffMock);
+
+ expect(state.diffFiles[0].file_hash).toBe(mockFile.file_hash);
+ expect(state.treeEntries[mockFile.file_path].diffLoaded).toBe(true);
+ });
});
describe('SET_COVERAGE_DATA', () => {
@@ -122,6 +155,17 @@ describe('DiffsStoreMutations', () => {
});
});
+ describe('SET_DIFF_TREE_ENTRY', () => {
+ it('should set tree entry', () => {
+ const file = getDiffFileMock();
+ const state = { treeEntries: { [file.file_path]: {} } };
+
+ mutations[types.SET_DIFF_TREE_ENTRY](state, file);
+
+ expect(state.treeEntries[file.file_path].diffLoaded).toBe(true);
+ });
+ });
+
describe('SET_DIFF_VIEW_TYPE', () => {
it('should set diff view type properly', () => {
const state = {};
@@ -1076,4 +1120,15 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[0].viewer.forceOpen).toBe(true);
});
});
+
+ describe('SET_PINNED_FILE_HASH', () => {
+ it('set pinned file hash', () => {
+ const state = {};
+ const file = getDiffFileMock();
+
+ mutations[types.SET_PINNED_FILE_HASH](state, file.file_hash);
+
+ expect(state.pinnedFileHash).toBe(file.file_hash);
+ });
+ });
});
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index 6331269d6e8..019ed663d82 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -476,6 +476,17 @@ describe('DiffsStoreUtils', () => {
expect(updatedFilesList).toEqual([mock, fakeNewFile]);
});
+ it('updates diff position', () => {
+ const priorFiles = [mock, { ...mock, file_hash: 'foo', file_path: 'foo' }];
+ const updatedFilesList = utils.prepareDiffData({
+ diff: { diff_files: [mock] },
+ priorFiles,
+ updatePosition: true,
+ });
+
+ expect(updatedFilesList[1].file_hash).toEqual(mock.file_hash);
+ });
+
it('completes an existing split diff without overwriting existing diffs', () => {
// The current state has a file that has only loaded inline lines
const priorFiles = [{ ...mock }];