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:
Diffstat (limited to 'spec/frontend/diffs/store/actions_spec.js')
-rw-r--r--spec/frontend/diffs/store/actions_spec.js499
1 files changed, 435 insertions, 64 deletions
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 78765204322..f883aea764f 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import Cookies from '~/lib/utils/cookies';
+import waitForPromises from 'helpers/wait_for_promises';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
@@ -8,12 +9,14 @@ import {
DIFF_VIEW_COOKIE_NAME,
INLINE_DIFF_VIEW_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
+ EVT_MR_PREPARED,
} from '~/diffs/constants';
+import { LOAD_SINGLE_DIFF_FAILED } 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';
import * as treeWorkerUtils from '~/diffs/utils/tree_worker_utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
import {
@@ -24,9 +27,15 @@ import {
} from '~/lib/utils/http_status';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '~/notes/event_hub';
+import diffsEventHub from '~/diffs/event_hub';
import { diffMetadata } from '../mock_data/diff_metadata';
-jest.mock('~/flash');
+jest.mock('~/alert');
+
+jest.mock('~/lib/utils/secret_detection', () => ({
+ confirmSensitiveAction: jest.fn(() => Promise.resolve(false)),
+ containsSensitiveToken: jest.requireActual('~/lib/utils/secret_detection').containsSensitiveToken,
+}));
describe('DiffsStoreActions', () => {
let mock;
@@ -69,6 +78,7 @@ 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';
@@ -83,6 +93,7 @@ describe('DiffsStoreActions', () => {
{
endpoint,
endpointBatch,
+ endpointDiffForPath,
endpointMetadata,
endpointCoverage,
projectPath,
@@ -93,6 +104,7 @@ describe('DiffsStoreActions', () => {
{
endpoint: '',
endpointBatch: '',
+ endpointDiffForPath: '',
endpointMetadata: '',
endpointCoverage: '',
projectPath: '',
@@ -106,6 +118,7 @@ describe('DiffsStoreActions', () => {
endpoint,
endpointMetadata,
endpointBatch,
+ endpointDiffForPath,
endpointCoverage,
projectPath,
dismissEndpoint,
@@ -131,6 +144,177 @@ describe('DiffsStoreActions', () => {
});
});
+ describe('fetchFileByFile', () => {
+ beforeEach(() => {
+ window.location.hash = 'e334a2a10f036c00151a04cea7938a5d4213a818';
+ });
+
+ it('should do nothing if there is no tree entry for the file ID', () => {
+ return testAction(diffActions.fetchFileByFile, {}, { flatBlobsList: [] }, [], []);
+ });
+
+ it('should do nothing if the tree entry for the file ID has already been marked as loaded', () => {
+ return testAction(
+ diffActions.fetchFileByFile,
+ {},
+ {
+ flatBlobsList: [
+ { fileHash: 'e334a2a10f036c00151a04cea7938a5d4213a818', diffLoaded: true },
+ ],
+ },
+ [],
+ [],
+ );
+ });
+
+ describe('when a tree entry exists for the file, but it has not been marked as loaded', () => {
+ let state;
+ let getters;
+ let commit;
+ let hubSpy;
+ const defaultParams = {
+ old_path: 'old/123',
+ new_path: 'new/123',
+ w: '1',
+ view: 'inline',
+ };
+ const endpointDiffForPath = '/diffs/set/endpoint/path';
+ const diffForPath = mergeUrlParams(defaultParams, endpointDiffForPath);
+ const treeEntry = {
+ fileHash: 'e334a2a10f036c00151a04cea7938a5d4213a818',
+ filePaths: { old: 'old/123', new: 'new/123' },
+ };
+ const fileResult = {
+ diff_files: [{ file_hash: 'e334a2a10f036c00151a04cea7938a5d4213a818' }],
+ };
+
+ beforeEach(() => {
+ commit = jest.fn();
+ state = {
+ endpointDiffForPath,
+ diffFiles: [],
+ };
+ getters = {
+ flatBlobsList: [treeEntry],
+ getDiffFileByHash(hash) {
+ return state.diffFiles?.find((entry) => entry.file_hash === hash);
+ },
+ };
+ hubSpy = jest.spyOn(diffsEventHub, '$emit');
+ });
+
+ it('does nothing if the file already exists in the loaded diff files', () => {
+ state.diffFiles = fileResult.diff_files;
+
+ return testAction(diffActions.fetchFileByFile, state, getters, [], []);
+ });
+
+ it('does some standard work every time', async () => {
+ mock.onGet(diffForPath).reply(HTTP_STATUS_OK, fileResult);
+
+ await diffActions.fetchFileByFile({ state, getters, commit });
+
+ expect(commit).toHaveBeenCalledWith(types.SET_BATCH_LOADING_STATE, 'loading');
+ expect(commit).toHaveBeenCalledWith(types.SET_RETRIEVING_BATCHES, true);
+
+ // wait for the mocked network request to return and start processing the .then
+ await waitForPromises();
+
+ expect(commit).toHaveBeenCalledWith(types.SET_DIFF_DATA_BATCH, fileResult);
+ expect(commit).toHaveBeenCalledWith(types.SET_BATCH_LOADING_STATE, 'loaded');
+
+ expect(hubSpy).toHaveBeenCalledWith('diffFilesModified');
+ });
+
+ it.each`
+ urlHash | diffFiles | expected
+ ${treeEntry.fileHash} | ${[]} | ${''}
+ ${'abcdef1234567890'} | ${fileResult.diff_files} | ${'e334a2a10f036c00151a04cea7938a5d4213a818'}
+ `(
+ "sets the current file to the first diff file ('$id') if it's not a note hash and there isn't a current ID set",
+ async ({ urlHash, diffFiles, expected }) => {
+ window.location.hash = urlHash;
+ mock.onGet(diffForPath).reply(HTTP_STATUS_OK, fileResult);
+ state.diffFiles = diffFiles;
+
+ await diffActions.fetchFileByFile({ state, getters, commit });
+
+ // wait for the mocked network request to return and start processing the .then
+ await waitForPromises();
+
+ expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, expected);
+ },
+ );
+
+ it('should fetch data without commit ID', async () => {
+ getters.commitId = null;
+ mock.onGet(diffForPath).reply(HTTP_STATUS_OK, fileResult);
+
+ await diffActions.fetchFileByFile({ state, getters, commit });
+
+ // wait for the mocked network request to return and start processing the .then
+ await waitForPromises();
+
+ // This tests that commit_id is NOT added, if there isn't one in the store
+ expect(mock.history.get[0].url).toEqual(diffForPath);
+ });
+
+ it('should fetch data with commit ID', async () => {
+ const finalPath = mergeUrlParams(
+ { ...defaultParams, commit_id: '123' },
+ endpointDiffForPath,
+ );
+
+ getters.commitId = '123';
+ mock.onGet(finalPath).reply(HTTP_STATUS_OK, fileResult);
+
+ await diffActions.fetchFileByFile({ state, getters, commit });
+
+ // wait for the mocked network request to return and start processing the .then
+ await waitForPromises();
+
+ expect(mock.history.get[0].url).toEqual(finalPath);
+ });
+
+ describe('version parameters', () => {
+ const diffId = '4';
+ const startSha = 'abc';
+ const pathRoot = 'a/a/-/merge_requests/1';
+
+ it('fetches the data when there is no mergeRequestDiff', async () => {
+ diffActions.fetchFileByFile({ state, getters, commit });
+
+ // wait for the mocked network request to return and start processing the .then
+ await waitForPromises();
+
+ expect(mock.history.get[0].url).toEqual(diffForPath);
+ });
+
+ it.each`
+ desc | versionPath | start_sha | diff_id
+ ${'no additional version information'} | ${`${pathRoot}?search=terms`} | ${undefined} | ${undefined}
+ ${'the diff_id'} | ${`${pathRoot}?diff_id=${diffId}`} | ${undefined} | ${diffId}
+ ${'the start_sha'} | ${`${pathRoot}?start_sha=${startSha}`} | ${startSha} | ${undefined}
+ ${'all available version information'} | ${`${pathRoot}?diff_id=${diffId}&start_sha=${startSha}`} | ${startSha} | ${diffId}
+ `('fetches the data and includes $desc', async ({ versionPath, start_sha, diff_id }) => {
+ const finalPath = mergeUrlParams(
+ { ...defaultParams, diff_id, start_sha },
+ endpointDiffForPath,
+ );
+ state.mergeRequestDiff = { version_path: versionPath };
+ mock.onGet(finalPath).reply(HTTP_STATUS_OK, fileResult);
+
+ diffActions.fetchFileByFile({ state, getters, commit });
+
+ // wait for the mocked network request to return and start processing the .then
+ await waitForPromises();
+
+ expect(mock.history.get[0].url).toEqual(finalPath);
+ });
+ });
+ });
+ });
+
describe('fetchDiffFilesBatch', () => {
it('should fetch batch diff files', () => {
const endpointBatch = '/fetch/diffs_batch';
@@ -213,36 +397,63 @@ describe('DiffsStoreActions', () => {
);
});
- it('should show a warning on 404 reponse', async () => {
- mock.onGet(endpointMetadata).reply(HTTP_STATUS_NOT_FOUND);
+ describe('on a 404 response', () => {
+ let dismissAlert;
- await testAction(
- diffActions.fetchDiffFilesMeta,
- {},
- { endpointMetadata, diffViewType: 'inline', showWhitespace: true },
- [{ type: types.SET_LOADING, payload: true }],
- [],
- );
+ beforeAll(() => {
+ dismissAlert = jest.fn();
- expect(createAlert).toHaveBeenCalledTimes(1);
- expect(createAlert).toHaveBeenCalledWith({
- message: expect.stringMatching(
- 'Building your merge request. Wait a few moments, then refresh this page.',
- ),
- variant: 'warning',
+ mock.onGet(endpointMetadata).reply(HTTP_STATUS_NOT_FOUND);
+ createAlert.mockImplementation(() => ({ dismiss: dismissAlert }));
+ });
+
+ it('should show a warning', async () => {
+ await testAction(
+ diffActions.fetchDiffFilesMeta,
+ {},
+ { endpointMetadata, diffViewType: 'inline', showWhitespace: true },
+ [{ type: types.SET_LOADING, payload: true }],
+ [],
+ );
+
+ expect(createAlert).toHaveBeenCalledTimes(1);
+ expect(createAlert).toHaveBeenCalledWith({
+ message: expect.stringMatching(
+ 'Building your merge request… This page will update when the build is complete.',
+ ),
+ variant: 'warning',
+ });
+ });
+
+ it("should attempt to close the alert if the MR reports that it's been prepared", async () => {
+ await testAction(
+ diffActions.fetchDiffFilesMeta,
+ {},
+ { endpointMetadata, diffViewType: 'inline', showWhitespace: true },
+ [{ type: types.SET_LOADING, payload: true }],
+ [],
+ );
+
+ diffsEventHub.$emit(EVT_MR_PREPARED);
+
+ expect(dismissAlert).toHaveBeenCalled();
});
});
it('should show no warning on any other status code', async () => {
mock.onGet(endpointMetadata).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
- await testAction(
- diffActions.fetchDiffFilesMeta,
- {},
- { endpointMetadata, diffViewType: 'inline', showWhitespace: true },
- [{ type: types.SET_LOADING, payload: true }],
- [],
- );
+ try {
+ await testAction(
+ diffActions.fetchDiffFilesMeta,
+ {},
+ { endpointMetadata, diffViewType: 'inline', showWhitespace: true },
+ [{ type: types.SET_LOADING, payload: true }],
+ [],
+ );
+ } catch (error) {
+ expect(error.response.status).toBe(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ }
expect(createAlert).not.toHaveBeenCalled();
});
@@ -265,7 +476,7 @@ describe('DiffsStoreActions', () => {
);
});
- it('should show flash on API error', async () => {
+ it('should show alert on API error', async () => {
mock.onGet(endpointCoverage).reply(HTTP_STATUS_BAD_REQUEST);
await testAction(diffActions.fetchCoverageFiles, {}, { endpointCoverage }, [], []);
@@ -389,7 +600,7 @@ describe('DiffsStoreActions', () => {
return testAction(
diffActions.assignDiscussionsToDiff,
[],
- { diffFiles: [] },
+ { diffFiles: [], flatBlobsList: [] },
[],
[{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
);
@@ -810,31 +1021,32 @@ describe('DiffsStoreActions', () => {
});
describe('saveDiffDiscussion', () => {
- it('dispatches actions', () => {
- const commitId = 'something';
- const formData = {
- diffFile: getDiffFileMock(),
- noteableData: {},
- };
- const note = {};
- const state = {
- commit: {
- id: commitId,
- },
- };
- const dispatch = jest.fn((name) => {
- switch (name) {
- case 'saveNote':
- return Promise.resolve({
- discussion: 'test',
- });
- case 'updateDiscussion':
- return Promise.resolve('discussion');
- default:
- return Promise.resolve({});
- }
- });
+ const dispatch = jest.fn((name) => {
+ switch (name) {
+ case 'saveNote':
+ return Promise.resolve({
+ discussion: 'test',
+ });
+ case 'updateDiscussion':
+ return Promise.resolve('discussion');
+ default:
+ return Promise.resolve({});
+ }
+ });
+
+ const commitId = 'something';
+ const formData = {
+ diffFile: getDiffFileMock(),
+ noteableData: {},
+ };
+ const note = {};
+ const state = {
+ commit: {
+ id: commitId,
+ },
+ };
+ it('dispatches actions', () => {
return diffActions.saveDiffDiscussion({ state, dispatch }, { note, formData }).then(() => {
expect(dispatch).toHaveBeenCalledTimes(5);
expect(dispatch).toHaveBeenNthCalledWith(1, 'saveNote', expect.any(Object), {
@@ -848,6 +1060,16 @@ describe('DiffsStoreActions', () => {
expect(dispatch).toHaveBeenNthCalledWith(3, 'assignDiscussionsToDiff', ['discussion']);
});
});
+
+ it('should not add note with sensitive token', async () => {
+ const sensitiveMessage = 'token: glpat-1234567890abcdefghij';
+
+ await diffActions.saveDiffDiscussion(
+ { state, dispatch },
+ { note: sensitiveMessage, formData },
+ );
+ expect(dispatch).not.toHaveBeenCalled();
+ });
});
describe('toggleTreeOpen', () => {
@@ -862,6 +1084,104 @@ describe('DiffsStoreActions', () => {
});
});
+ describe('goToFile', () => {
+ const getters = {};
+ const file = { path: 'path' };
+ const fileHash = 'test';
+ let state;
+ let dispatch;
+ let commit;
+
+ beforeEach(() => {
+ getters.isTreePathLoaded = () => false;
+ state = {
+ viewDiffsFileByFile: true,
+ treeEntries: {
+ path: {
+ fileHash,
+ },
+ },
+ };
+ commit = jest.fn();
+ dispatch = jest.fn().mockResolvedValue();
+ });
+
+ it('immediately defers to scrollToFile if the app is not in file-by-file mode', () => {
+ state.viewDiffsFileByFile = false;
+
+ diffActions.goToFile({ state, dispatch }, file);
+
+ expect(dispatch).toHaveBeenCalledWith('scrollToFile', file);
+ });
+
+ describe('when the app is in fileByFile mode', () => {
+ describe('when the singleFileFileByFile feature flag is enabled', () => {
+ it('commits SET_CURRENT_DIFF_FILE', () => {
+ diffActions.goToFile(
+ { state, commit, dispatch, getters },
+ { path: file.path, singleFile: true },
+ );
+
+ expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, fileHash);
+ });
+
+ it('does nothing more if the path has already been loaded', () => {
+ getters.isTreePathLoaded = () => true;
+
+ diffActions.goToFile(
+ { state, dispatch, getters, commit },
+ { path: file.path, singleFile: true },
+ );
+
+ expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, fileHash);
+ expect(dispatch).toHaveBeenCalledTimes(0);
+ });
+
+ describe('when the tree entry has not been loaded', () => {
+ it('updates location hash', () => {
+ diffActions.goToFile(
+ { state, commit, getters, dispatch },
+ { path: file.path, singleFile: true },
+ );
+
+ expect(document.location.hash).toBe('#test');
+ });
+
+ it('loads the file and then scrolls to it', async () => {
+ diffActions.goToFile(
+ { state, commit, getters, dispatch },
+ { path: file.path, singleFile: true },
+ );
+
+ // Wait for the fetchFileByFile dispatch to return, to trigger scrollToFile
+ await waitForPromises();
+
+ expect(dispatch).toHaveBeenCalledWith('fetchFileByFile');
+ expect(dispatch).toHaveBeenCalledWith('scrollToFile', file);
+ expect(dispatch).toHaveBeenCalledTimes(2);
+ });
+
+ it('shows an alert when there was an error fetching the file', async () => {
+ dispatch = jest.fn().mockRejectedValue();
+
+ diffActions.goToFile(
+ { state, commit, getters, dispatch },
+ { path: file.path, singleFile: true },
+ );
+
+ // 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),
+ });
+ });
+ });
+ });
+ });
+ });
+
describe('scrollToFile', () => {
let commit;
const getters = { isVirtualScrollingEnabled: false };
@@ -1007,20 +1327,14 @@ describe('DiffsStoreActions', () => {
describe('setShowWhitespace', () => {
const endpointUpdateUser = 'user/prefs';
let putSpy;
- let gon;
beforeEach(() => {
putSpy = jest.spyOn(axios, 'put');
- gon = window.gon;
mock.onPut(endpointUpdateUser).reply(HTTP_STATUS_OK, {});
jest.spyOn(eventHub, '$emit').mockImplementation();
});
- afterEach(() => {
- window.gon = gon;
- });
-
it('commits SET_SHOW_WHITESPACE', () => {
return testAction(
diffActions.setShowWhitespace,
@@ -1390,42 +1704,89 @@ describe('DiffsStoreActions', () => {
);
});
+ describe('rereadNoteHash', () => {
+ beforeEach(() => {
+ window.location.hash = 'note_123';
+ });
+
+ it('dispatches setCurrentDiffFileIdFromNote if the hash is a note URL', () => {
+ window.location.hash = 'note_123';
+
+ return testAction(
+ diffActions.rereadNoteHash,
+ {},
+ {},
+ [],
+ [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
+ );
+ });
+
+ it('dispatches fetchFileByFile if the app is in fileByFile mode', () => {
+ window.location.hash = 'note_123';
+
+ return testAction(
+ diffActions.rereadNoteHash,
+ {},
+ { viewDiffsFileByFile: true },
+ [],
+ [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }, { type: 'fetchFileByFile' }],
+ );
+ });
+
+ it('does not try to fetch the diff file if the app is not in fileByFile mode', () => {
+ window.location.hash = 'note_123';
+
+ return testAction(
+ diffActions.rereadNoteHash,
+ {},
+ { viewDiffsFileByFile: false },
+ [],
+ [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
+ );
+ });
+
+ it('does nothing if the hash is not a note URL', () => {
+ window.location.hash = 'abcdef1234567890';
+
+ return testAction(diffActions.rereadNoteHash, {}, {}, [], []);
+ });
+ });
+
describe('setCurrentDiffFileIdFromNote', () => {
it('commits SET_CURRENT_DIFF_FILE', () => {
const commit = jest.fn();
- const state = { diffFiles: [{ file_hash: '123' }] };
+ const getters = { flatBlobsList: [{ fileHash: '123' }] };
const rootGetters = {
getDiscussion: () => ({ diff_file: { file_hash: '123' } }),
notesById: { 1: { discussion_id: '2' } },
};
- diffActions.setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
+ diffActions.setCurrentDiffFileIdFromNote({ commit, getters, rootGetters }, '1');
expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, '123');
});
it('does not commit SET_CURRENT_DIFF_FILE when discussion has no diff_file', () => {
const commit = jest.fn();
- const state = { diffFiles: [{ file_hash: '123' }] };
const rootGetters = {
getDiscussion: () => ({ id: '1' }),
notesById: { 1: { discussion_id: '2' } },
};
- diffActions.setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
+ diffActions.setCurrentDiffFileIdFromNote({ commit, rootGetters }, '1');
expect(commit).not.toHaveBeenCalled();
});
it('does not commit SET_CURRENT_DIFF_FILE when diff file does not exist', () => {
const commit = jest.fn();
- const state = { diffFiles: [{ file_hash: '123' }] };
+ const getters = { flatBlobsList: [{ fileHash: '123' }] };
const rootGetters = {
getDiscussion: () => ({ diff_file: { file_hash: '124' } }),
notesById: { 1: { discussion_id: '2' } },
};
- diffActions.setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
+ diffActions.setCurrentDiffFileIdFromNote({ commit, getters, rootGetters }, '1');
expect(commit).not.toHaveBeenCalled();
});
@@ -1435,12 +1796,22 @@ describe('DiffsStoreActions', () => {
it('commits SET_CURRENT_DIFF_FILE', () => {
return testAction(
diffActions.navigateToDiffFileIndex,
- 0,
- { diffFiles: [{ file_hash: '123' }] },
+ { index: 0, singleFile: false },
+ { flatBlobsList: [{ fileHash: '123' }] },
[{ type: types.SET_CURRENT_DIFF_FILE, payload: '123' }],
[],
);
});
+
+ it('dispatches the fetchFileByFile action when the state value viewDiffsFileByFile is true and the single-file file-by-file feature flag is enabled', () => {
+ return testAction(
+ diffActions.navigateToDiffFileIndex,
+ { index: 0, singleFile: true },
+ { viewDiffsFileByFile: true, flatBlobsList: [{ fileHash: '123' }] },
+ [{ type: types.SET_CURRENT_DIFF_FILE, payload: '123' }],
+ [{ type: 'fetchFileByFile' }],
+ );
+ });
});
describe('setFileByFile', () => {