1892 lines
58 KiB
JavaScript
1892 lines
58 KiB
JavaScript
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';
|
|
import { getDiffFileMock } from 'jest/diffs/mock_data/diff_file';
|
|
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 '~/alert';
|
|
import axios from '~/lib/utils/axios_utils';
|
|
import * as commonUtils from '~/lib/utils/common_utils';
|
|
import {
|
|
HTTP_STATUS_BAD_REQUEST,
|
|
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
|
HTTP_STATUS_NOT_FOUND,
|
|
HTTP_STATUS_OK,
|
|
} 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('~/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;
|
|
|
|
useLocalStorageSpy();
|
|
|
|
const originalMethods = {
|
|
requestAnimationFrame: global.requestAnimationFrame,
|
|
requestIdleCallback: global.requestIdleCallback,
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.spyOn(window.history, 'pushState');
|
|
jest.spyOn(commonUtils, 'historyPushState');
|
|
jest.spyOn(commonUtils, 'handleLocationHash').mockImplementation(() => null);
|
|
jest.spyOn(commonUtils, 'scrollToElement').mockImplementation(() => null);
|
|
jest.spyOn(utils, 'convertExpandLines').mockImplementation(() => null);
|
|
jest.spyOn(utils, 'idleCallback').mockImplementation(() => null);
|
|
['requestAnimationFrame', 'requestIdleCallback'].forEach((method) => {
|
|
global[method] = (cb) => {
|
|
cb({ timeRemaining: () => 10 });
|
|
};
|
|
});
|
|
});
|
|
|
|
beforeEach(() => {
|
|
mock = new MockAdapter(axios);
|
|
});
|
|
|
|
afterEach(() => {
|
|
['requestAnimationFrame', 'requestIdleCallback'].forEach((method) => {
|
|
global[method] = originalMethods[method];
|
|
});
|
|
createAlert.mockClear();
|
|
mock.restore();
|
|
});
|
|
|
|
describe('setBaseConfig', () => {
|
|
it('should set given endpoint and project path', () => {
|
|
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';
|
|
const showSuggestPopover = false;
|
|
const mrReviews = {
|
|
a: ['z', 'hash:a'],
|
|
b: ['y', 'hash:a'],
|
|
};
|
|
|
|
return testAction(
|
|
diffActions.setBaseConfig,
|
|
{
|
|
endpoint,
|
|
endpointBatch,
|
|
endpointDiffForPath,
|
|
endpointMetadata,
|
|
endpointCoverage,
|
|
projectPath,
|
|
dismissEndpoint,
|
|
showSuggestPopover,
|
|
mrReviews,
|
|
},
|
|
{
|
|
endpoint: '',
|
|
endpointBatch: '',
|
|
endpointDiffForPath: '',
|
|
endpointMetadata: '',
|
|
endpointCoverage: '',
|
|
projectPath: '',
|
|
dismissEndpoint: '',
|
|
showSuggestPopover: true,
|
|
},
|
|
[
|
|
{
|
|
type: types.SET_BASE_CONFIG,
|
|
payload: {
|
|
endpoint,
|
|
endpointMetadata,
|
|
endpointBatch,
|
|
endpointDiffForPath,
|
|
endpointCoverage,
|
|
projectPath,
|
|
dismissEndpoint,
|
|
showSuggestPopover,
|
|
mrReviews,
|
|
},
|
|
},
|
|
{
|
|
type: types.SET_DIFF_FILE_VIEWED,
|
|
payload: { id: 'z', seen: true },
|
|
},
|
|
{
|
|
type: types.SET_DIFF_FILE_VIEWED,
|
|
payload: { id: 'a', seen: true },
|
|
},
|
|
{
|
|
type: types.SET_DIFF_FILE_VIEWED,
|
|
payload: { id: 'y', seen: true },
|
|
},
|
|
],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
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';
|
|
const res1 = { diff_files: [{ file_hash: 'test' }], pagination: { total_pages: 7 } };
|
|
const res2 = { diff_files: [{ file_hash: 'test2' }], pagination: { total_pages: 7 } };
|
|
mock
|
|
.onGet(
|
|
mergeUrlParams(
|
|
{
|
|
w: '1',
|
|
view: 'inline',
|
|
page: 0,
|
|
per_page: 5,
|
|
},
|
|
endpointBatch,
|
|
),
|
|
)
|
|
.reply(HTTP_STATUS_OK, res1)
|
|
.onGet(
|
|
mergeUrlParams(
|
|
{
|
|
w: '1',
|
|
view: 'inline',
|
|
page: 5,
|
|
per_page: 7,
|
|
},
|
|
endpointBatch,
|
|
),
|
|
)
|
|
.reply(HTTP_STATUS_OK, res2);
|
|
|
|
return testAction(
|
|
diffActions.fetchDiffFilesBatch,
|
|
{},
|
|
{ endpointBatch, diffViewType: 'inline', diffFiles: [] },
|
|
[
|
|
{ type: types.SET_BATCH_LOADING_STATE, payload: 'loading' },
|
|
{ type: types.SET_RETRIEVING_BATCHES, payload: true },
|
|
{ type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } },
|
|
{ type: types.SET_BATCH_LOADING_STATE, payload: 'loaded' },
|
|
{ type: types.SET_CURRENT_DIFF_FILE, payload: 'test' },
|
|
{ type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } },
|
|
{ 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' },
|
|
],
|
|
[{ type: 'startRenderDiffsQueue' }, { type: 'startRenderDiffsQueue' }],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('fetchDiffFilesMeta', () => {
|
|
const endpointMetadata = '/fetch/diffs_metadata.json?view=inline&w=0';
|
|
const noFilesData = { ...diffMetadata };
|
|
|
|
beforeEach(() => {
|
|
delete noFilesData.diff_files;
|
|
});
|
|
|
|
it('should fetch diff meta information', () => {
|
|
mock.onGet(endpointMetadata).reply(HTTP_STATUS_OK, diffMetadata);
|
|
|
|
return testAction(
|
|
diffActions.fetchDiffFilesMeta,
|
|
{},
|
|
{ endpointMetadata, diffViewType: 'inline', showWhitespace: true },
|
|
[
|
|
{ type: types.SET_LOADING, payload: true },
|
|
{ type: types.SET_LOADING, payload: false },
|
|
{ type: types.SET_MERGE_REQUEST_DIFFS, payload: diffMetadata.merge_request_diffs },
|
|
{ type: types.SET_DIFF_METADATA, payload: noFilesData },
|
|
// Workers are synchronous in Jest environment (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58805)
|
|
{
|
|
type: types.SET_TREE_DATA,
|
|
payload: treeWorkerUtils.generateTreeList(diffMetadata.diff_files),
|
|
},
|
|
],
|
|
[],
|
|
);
|
|
});
|
|
|
|
describe('on a 404 response', () => {
|
|
let dismissAlert;
|
|
|
|
beforeAll(() => {
|
|
dismissAlert = jest.fn();
|
|
|
|
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);
|
|
|
|
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();
|
|
});
|
|
});
|
|
|
|
describe('fetchCoverageFiles', () => {
|
|
const endpointCoverage = '/fetch';
|
|
|
|
it('should commit SET_COVERAGE_DATA with received response', () => {
|
|
const data = { files: { 'app.js': { 1: 0, 2: 1 } } };
|
|
|
|
mock.onGet(endpointCoverage).reply(HTTP_STATUS_OK, { data });
|
|
|
|
return testAction(
|
|
diffActions.fetchCoverageFiles,
|
|
{},
|
|
{ endpointCoverage },
|
|
[{ type: types.SET_COVERAGE_DATA, payload: { data } }],
|
|
[],
|
|
);
|
|
});
|
|
|
|
it('should show alert on API error', async () => {
|
|
mock.onGet(endpointCoverage).reply(HTTP_STATUS_BAD_REQUEST);
|
|
|
|
await testAction(diffActions.fetchCoverageFiles, {}, { endpointCoverage }, [], []);
|
|
expect(createAlert).toHaveBeenCalledTimes(1);
|
|
expect(createAlert).toHaveBeenCalledWith({
|
|
message: expect.stringMatching('Something went wrong'),
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('setHighlightedRow', () => {
|
|
it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => {
|
|
return testAction(diffActions.setHighlightedRow, 'ABC_123', {}, [
|
|
{ type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' },
|
|
{ type: types.SET_CURRENT_DIFF_FILE, payload: 'ABC' },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('assignDiscussionsToDiff', () => {
|
|
afterEach(() => {
|
|
window.location.hash = '';
|
|
});
|
|
|
|
it('should merge discussions into diffs', () => {
|
|
window.location.hash = 'ABC_123';
|
|
|
|
const state = {
|
|
diffFiles: [
|
|
{
|
|
file_hash: 'ABC',
|
|
parallel_diff_lines: [
|
|
{
|
|
left: {
|
|
line_code: 'ABC_1_1',
|
|
discussions: [],
|
|
},
|
|
right: {
|
|
line_code: 'ABC_1_1',
|
|
discussions: [],
|
|
},
|
|
},
|
|
],
|
|
highlighted_diff_lines: [
|
|
{
|
|
line_code: 'ABC_1_1',
|
|
discussions: [],
|
|
old_line: 5,
|
|
new_line: null,
|
|
},
|
|
],
|
|
diff_refs: {
|
|
base_sha: 'abc',
|
|
head_sha: 'def',
|
|
start_sha: 'ghi',
|
|
},
|
|
new_path: 'file1',
|
|
old_path: 'file2',
|
|
},
|
|
],
|
|
};
|
|
|
|
const diffPosition = {
|
|
base_sha: 'abc',
|
|
head_sha: 'def',
|
|
start_sha: 'ghi',
|
|
new_line: null,
|
|
new_path: 'file1',
|
|
old_line: 5,
|
|
old_path: 'file2',
|
|
};
|
|
|
|
const singleDiscussion = {
|
|
line_code: 'ABC_1_1',
|
|
diff_discussion: {},
|
|
diff_file: {
|
|
file_hash: 'ABC',
|
|
},
|
|
file_hash: 'ABC',
|
|
resolvable: true,
|
|
position: diffPosition,
|
|
original_position: diffPosition,
|
|
};
|
|
|
|
const discussions = [singleDiscussion];
|
|
|
|
return testAction(
|
|
diffActions.assignDiscussionsToDiff,
|
|
discussions,
|
|
state,
|
|
[
|
|
{
|
|
type: types.SET_LINE_DISCUSSIONS_FOR_FILE,
|
|
payload: {
|
|
discussion: singleDiscussion,
|
|
diffPositionByLineCode: {
|
|
ABC_1_1: {
|
|
base_sha: 'abc',
|
|
head_sha: 'def',
|
|
start_sha: 'ghi',
|
|
new_line: null,
|
|
new_path: 'file1',
|
|
old_line: 5,
|
|
old_path: 'file2',
|
|
line_range: null,
|
|
line_code: 'ABC_1_1',
|
|
position_type: 'text',
|
|
},
|
|
},
|
|
hash: 'ABC_123',
|
|
},
|
|
},
|
|
],
|
|
[],
|
|
);
|
|
});
|
|
|
|
it('dispatches setCurrentDiffFileIdFromNote with note ID', () => {
|
|
window.location.hash = 'note_123';
|
|
|
|
return testAction(
|
|
diffActions.assignDiscussionsToDiff,
|
|
[],
|
|
{ diffFiles: [], flatBlobsList: [] },
|
|
[],
|
|
[{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('removeDiscussionsFromDiff', () => {
|
|
it('should remove discussions from diffs', () => {
|
|
const state = {
|
|
diffFiles: [
|
|
{
|
|
file_hash: 'ABC',
|
|
parallel_diff_lines: [
|
|
{
|
|
left: {
|
|
line_code: 'ABC_1_1',
|
|
discussions: [
|
|
{
|
|
id: 1,
|
|
},
|
|
],
|
|
},
|
|
right: {
|
|
line_code: 'ABC_1_1',
|
|
discussions: [],
|
|
},
|
|
},
|
|
],
|
|
highlighted_diff_lines: [
|
|
{
|
|
line_code: 'ABC_1_1',
|
|
discussions: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const singleDiscussion = {
|
|
id: '1',
|
|
file_hash: 'ABC',
|
|
line_code: 'ABC_1_1',
|
|
};
|
|
|
|
return testAction(
|
|
diffActions.removeDiscussionsFromDiff,
|
|
singleDiscussion,
|
|
state,
|
|
[
|
|
{
|
|
type: types.REMOVE_LINE_DISCUSSIONS_FOR_FILE,
|
|
payload: {
|
|
id: '1',
|
|
fileHash: 'ABC',
|
|
lineCode: 'ABC_1_1',
|
|
},
|
|
},
|
|
],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('startRenderDiffsQueue', () => {
|
|
it('should set all files to RENDER_FILE', () => {
|
|
const state = {
|
|
diffFiles: [
|
|
{
|
|
id: 1,
|
|
renderIt: false,
|
|
viewer: {
|
|
automaticallyCollapsed: false,
|
|
},
|
|
},
|
|
{
|
|
id: 2,
|
|
renderIt: false,
|
|
viewer: {
|
|
automaticallyCollapsed: false,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const pseudoCommit = (commitType, file) => {
|
|
expect(commitType).toBe(types.RENDER_FILE);
|
|
Object.assign(file, {
|
|
renderIt: true,
|
|
});
|
|
};
|
|
|
|
diffActions.startRenderDiffsQueue({ state, commit: pseudoCommit });
|
|
|
|
expect(state.diffFiles[0].renderIt).toBe(true);
|
|
expect(state.diffFiles[1].renderIt).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('setInlineDiffViewType', () => {
|
|
it('should set diff view type to inline and also set the cookie properly', async () => {
|
|
await testAction(
|
|
diffActions.setInlineDiffViewType,
|
|
null,
|
|
{},
|
|
[{ type: types.SET_DIFF_VIEW_TYPE, payload: INLINE_DIFF_VIEW_TYPE }],
|
|
[],
|
|
);
|
|
expect(Cookies.get('diff_view')).toEqual(INLINE_DIFF_VIEW_TYPE);
|
|
});
|
|
});
|
|
|
|
describe('setParallelDiffViewType', () => {
|
|
it('should set diff view type to parallel and also set the cookie properly', async () => {
|
|
await testAction(
|
|
diffActions.setParallelDiffViewType,
|
|
null,
|
|
{},
|
|
[{ type: types.SET_DIFF_VIEW_TYPE, payload: PARALLEL_DIFF_VIEW_TYPE }],
|
|
[],
|
|
);
|
|
expect(Cookies.get(DIFF_VIEW_COOKIE_NAME)).toEqual(PARALLEL_DIFF_VIEW_TYPE);
|
|
});
|
|
});
|
|
|
|
describe('showCommentForm', () => {
|
|
it('should call mutation to show comment form', () => {
|
|
const payload = { lineCode: 'lineCode', fileHash: 'hash' };
|
|
|
|
return testAction(
|
|
diffActions.showCommentForm,
|
|
payload,
|
|
{},
|
|
[{ type: types.TOGGLE_LINE_HAS_FORM, payload: { ...payload, hasForm: true } }],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('cancelCommentForm', () => {
|
|
it('should call mutation to cancel comment form', () => {
|
|
const payload = { lineCode: 'lineCode', fileHash: 'hash' };
|
|
|
|
return testAction(
|
|
diffActions.cancelCommentForm,
|
|
payload,
|
|
{},
|
|
[{ type: types.TOGGLE_LINE_HAS_FORM, payload: { ...payload, hasForm: false } }],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('loadMoreLines', () => {
|
|
it('should call mutation to show comment form', () => {
|
|
const endpoint = '/diffs/load/more/lines';
|
|
const params = { since: 6, to: 26 };
|
|
const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
|
|
const fileHash = 'ff9200';
|
|
const isExpandDown = false;
|
|
const nextLineNumbers = {};
|
|
const options = { endpoint, params, lineNumbers, fileHash, isExpandDown, nextLineNumbers };
|
|
const contextLines = { contextLines: [{ lineCode: 6 }] };
|
|
mock.onGet(endpoint).reply(HTTP_STATUS_OK, contextLines);
|
|
|
|
return testAction(
|
|
diffActions.loadMoreLines,
|
|
options,
|
|
{},
|
|
[
|
|
{
|
|
type: types.ADD_CONTEXT_LINES,
|
|
payload: { lineNumbers, contextLines, params, fileHash, isExpandDown, nextLineNumbers },
|
|
},
|
|
],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('loadCollapsedDiff', () => {
|
|
const state = { showWhitespace: true };
|
|
it('should fetch data and call mutation with response and the give parameter', () => {
|
|
const file = { hash: 123, load_collapsed_diff_url: '/load/collapsed/diff/url' };
|
|
const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] };
|
|
const commit = jest.fn();
|
|
mock.onGet(file.loadCollapsedDiffUrl).reply(HTTP_STATUS_OK, data);
|
|
|
|
return diffActions
|
|
.loadCollapsedDiff({ commit, getters: { commitId: null }, state }, file)
|
|
.then(() => {
|
|
expect(commit).toHaveBeenCalledWith(types.ADD_COLLAPSED_DIFFS, { file, data });
|
|
});
|
|
});
|
|
|
|
it('should fetch data without commit ID', () => {
|
|
const file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
|
|
const getters = {
|
|
commitId: null,
|
|
};
|
|
|
|
jest.spyOn(axios, 'get').mockReturnValue(Promise.resolve({ data: {} }));
|
|
|
|
diffActions.loadCollapsedDiff({ commit() {}, getters, state }, file);
|
|
|
|
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
|
|
params: { commit_id: null, w: '0' },
|
|
});
|
|
});
|
|
|
|
it('should fetch data with commit ID', () => {
|
|
const file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
|
|
const getters = {
|
|
commitId: '123',
|
|
};
|
|
|
|
jest.spyOn(axios, 'get').mockReturnValue(Promise.resolve({ data: {} }));
|
|
|
|
diffActions.loadCollapsedDiff({ commit() {}, getters, state }, file);
|
|
|
|
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
|
|
params: { commit_id: '123', w: '0' },
|
|
});
|
|
});
|
|
|
|
describe('version parameters', () => {
|
|
const diffId = '4';
|
|
const startSha = 'abc';
|
|
const pathRoot = 'a/a/-/merge_requests/1';
|
|
let file;
|
|
let getters;
|
|
|
|
beforeAll(() => {
|
|
file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
|
|
getters = {};
|
|
});
|
|
|
|
beforeEach(() => {
|
|
jest.spyOn(axios, 'get').mockReturnValue(Promise.resolve({ data: {} }));
|
|
});
|
|
|
|
it('fetches the data when there is no mergeRequestDiff', () => {
|
|
diffActions.loadCollapsedDiff({ commit() {}, getters, state }, file);
|
|
|
|
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
|
|
params: expect.any(Object),
|
|
});
|
|
});
|
|
|
|
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', ({ versionPath, start_sha, diff_id }) => {
|
|
jest.spyOn(axios, 'get').mockReturnValue(Promise.resolve({ data: {} }));
|
|
|
|
diffActions.loadCollapsedDiff(
|
|
{ commit() {}, getters, state: { mergeRequestDiff: { version_path: versionPath } } },
|
|
file,
|
|
);
|
|
|
|
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
|
|
params: expect.objectContaining({ start_sha, diff_id }),
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('toggleFileDiscussions', () => {
|
|
it('should dispatch collapseDiscussion when all discussions are expanded', () => {
|
|
const getters = {
|
|
getDiffFileDiscussions: jest.fn(() => [{ id: 1 }]),
|
|
diffHasAllExpandedDiscussions: jest.fn(() => true),
|
|
diffHasAllCollapsedDiscussions: jest.fn(() => false),
|
|
};
|
|
|
|
const dispatch = jest.fn();
|
|
|
|
diffActions.toggleFileDiscussions({ getters, dispatch });
|
|
|
|
expect(dispatch).toHaveBeenCalledWith(
|
|
'collapseDiscussion',
|
|
{ discussionId: 1 },
|
|
{ root: true },
|
|
);
|
|
});
|
|
|
|
it('should dispatch expandDiscussion when all discussions are collapsed', () => {
|
|
const getters = {
|
|
getDiffFileDiscussions: jest.fn(() => [{ id: 1 }]),
|
|
diffHasAllExpandedDiscussions: jest.fn(() => false),
|
|
diffHasAllCollapsedDiscussions: jest.fn(() => true),
|
|
};
|
|
|
|
const dispatch = jest.fn();
|
|
|
|
diffActions.toggleFileDiscussions({ getters, dispatch });
|
|
|
|
expect(dispatch).toHaveBeenCalledWith(
|
|
'expandDiscussion',
|
|
{ discussionId: 1 },
|
|
{ root: true },
|
|
);
|
|
});
|
|
|
|
it('should dispatch expandDiscussion when some discussions are collapsed and others are expanded for the collapsed discussion', () => {
|
|
const getters = {
|
|
getDiffFileDiscussions: jest.fn(() => [{ expanded: false, id: 1 }]),
|
|
diffHasAllExpandedDiscussions: jest.fn(() => false),
|
|
diffHasAllCollapsedDiscussions: jest.fn(() => false),
|
|
};
|
|
|
|
const dispatch = jest.fn();
|
|
|
|
diffActions.toggleFileDiscussions({ getters, dispatch });
|
|
|
|
expect(dispatch).toHaveBeenCalledWith(
|
|
'expandDiscussion',
|
|
{ discussionId: 1 },
|
|
{ root: true },
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('scrollToLineIfNeededInline', () => {
|
|
const lineMock = {
|
|
line_code: 'ABC_123',
|
|
};
|
|
|
|
it('should not call handleLocationHash when there is not hash', () => {
|
|
window.location.hash = '';
|
|
|
|
diffActions.scrollToLineIfNeededInline({}, lineMock);
|
|
|
|
expect(commonUtils.handleLocationHash).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not call handleLocationHash when the hash does not match any line', () => {
|
|
window.location.hash = 'XYZ_456';
|
|
|
|
diffActions.scrollToLineIfNeededInline({}, lineMock);
|
|
|
|
expect(commonUtils.handleLocationHash).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should call handleLocationHash only when the hash matches a line', () => {
|
|
window.location.hash = 'ABC_123';
|
|
|
|
diffActions.scrollToLineIfNeededInline(
|
|
{},
|
|
{
|
|
lineCode: 'ABC_456',
|
|
},
|
|
);
|
|
diffActions.scrollToLineIfNeededInline({}, lineMock);
|
|
diffActions.scrollToLineIfNeededInline(
|
|
{},
|
|
{
|
|
lineCode: 'XYZ_456',
|
|
},
|
|
);
|
|
|
|
expect(commonUtils.handleLocationHash).toHaveBeenCalled();
|
|
expect(commonUtils.handleLocationHash).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('scrollToLineIfNeededParallel', () => {
|
|
const lineMock = {
|
|
left: null,
|
|
right: {
|
|
line_code: 'ABC_123',
|
|
},
|
|
};
|
|
|
|
it('should not call handleLocationHash when there is not hash', () => {
|
|
window.location.hash = '';
|
|
|
|
diffActions.scrollToLineIfNeededParallel({}, lineMock);
|
|
|
|
expect(commonUtils.handleLocationHash).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not call handleLocationHash when the hash does not match any line', () => {
|
|
window.location.hash = 'XYZ_456';
|
|
|
|
diffActions.scrollToLineIfNeededParallel({}, lineMock);
|
|
|
|
expect(commonUtils.handleLocationHash).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should call handleLocationHash only when the hash matches a line', () => {
|
|
window.location.hash = 'ABC_123';
|
|
|
|
diffActions.scrollToLineIfNeededParallel(
|
|
{},
|
|
{
|
|
left: null,
|
|
right: {
|
|
lineCode: 'ABC_456',
|
|
},
|
|
},
|
|
);
|
|
diffActions.scrollToLineIfNeededParallel({}, lineMock);
|
|
diffActions.scrollToLineIfNeededParallel(
|
|
{},
|
|
{
|
|
left: null,
|
|
right: {
|
|
lineCode: 'XYZ_456',
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(commonUtils.handleLocationHash).toHaveBeenCalled();
|
|
expect(commonUtils.handleLocationHash).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('saveDiffDiscussion', () => {
|
|
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), {
|
|
root: true,
|
|
});
|
|
|
|
const postData = dispatch.mock.calls[0][1];
|
|
expect(postData.data.note.commit_id).toBe(commitId);
|
|
|
|
expect(dispatch).toHaveBeenNthCalledWith(2, 'updateDiscussion', 'test', { root: true });
|
|
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', () => {
|
|
it('commits TOGGLE_FOLDER_OPEN', () => {
|
|
return testAction(
|
|
diffActions.toggleTreeOpen,
|
|
'path',
|
|
{},
|
|
[{ type: types.TOGGLE_FOLDER_OPEN, payload: 'path' }],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
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 };
|
|
|
|
beforeEach(() => {
|
|
commit = jest.fn();
|
|
});
|
|
|
|
it('updates location hash', () => {
|
|
const state = {
|
|
treeEntries: {
|
|
path: {
|
|
fileHash: 'test',
|
|
},
|
|
},
|
|
};
|
|
|
|
diffActions.scrollToFile({ state, commit, getters }, { path: 'path' });
|
|
|
|
expect(document.location.hash).toBe('#test');
|
|
});
|
|
|
|
it('commits SET_CURRENT_DIFF_FILE', () => {
|
|
const state = {
|
|
treeEntries: {
|
|
path: {
|
|
fileHash: 'test',
|
|
},
|
|
},
|
|
};
|
|
|
|
diffActions.scrollToFile({ state, commit, getters }, { path: 'path' });
|
|
|
|
expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, 'test');
|
|
});
|
|
});
|
|
|
|
describe('setShowTreeList', () => {
|
|
it('commits toggle', () => {
|
|
return testAction(
|
|
diffActions.setShowTreeList,
|
|
{ showTreeList: true },
|
|
{},
|
|
[{ type: types.SET_SHOW_TREE_LIST, payload: true }],
|
|
[],
|
|
);
|
|
});
|
|
|
|
it('updates localStorage', () => {
|
|
jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
|
|
|
|
diffActions.setShowTreeList({ commit() {} }, { showTreeList: true });
|
|
|
|
expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true);
|
|
});
|
|
|
|
it('does not update localStorage', () => {
|
|
jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
|
|
|
|
diffActions.setShowTreeList({ commit() {} }, { showTreeList: true, saving: false });
|
|
|
|
expect(localStorage.setItem).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('renderFileForDiscussionId', () => {
|
|
const rootState = {
|
|
notes: {
|
|
discussions: [
|
|
{
|
|
id: '123',
|
|
diff_file: {
|
|
file_hash: 'HASH',
|
|
},
|
|
},
|
|
{
|
|
id: '456',
|
|
diff_file: {
|
|
file_hash: 'HASH',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
};
|
|
let commit;
|
|
let $emit;
|
|
const state = ({ collapsed, renderIt }) => ({
|
|
diffFiles: [
|
|
{
|
|
file_hash: 'HASH',
|
|
viewer: {
|
|
automaticallyCollapsed: collapsed,
|
|
},
|
|
renderIt,
|
|
},
|
|
],
|
|
});
|
|
|
|
beforeEach(() => {
|
|
commit = jest.fn();
|
|
$emit = jest.spyOn(eventHub, '$emit');
|
|
});
|
|
|
|
it('renders and expands file for the given discussion id', () => {
|
|
const localState = state({ collapsed: true, renderIt: false });
|
|
|
|
diffActions.renderFileForDiscussionId({ rootState, state: localState, commit }, '123');
|
|
|
|
expect(commit).toHaveBeenCalledWith('RENDER_FILE', localState.diffFiles[0]);
|
|
expect($emit).toHaveBeenCalledTimes(1);
|
|
expect(commonUtils.scrollToElement).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('jumps to discussion on already rendered and expanded file', () => {
|
|
const localState = state({ collapsed: false, renderIt: true });
|
|
|
|
diffActions.renderFileForDiscussionId({ rootState, state: localState, commit }, '123');
|
|
|
|
expect(commit).not.toHaveBeenCalled();
|
|
expect($emit).toHaveBeenCalledTimes(1);
|
|
expect(commonUtils.scrollToElement).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('setRenderTreeList', () => {
|
|
it('commits SET_RENDER_TREE_LIST', () => {
|
|
return testAction(
|
|
diffActions.setRenderTreeList,
|
|
{ renderTreeList: true },
|
|
{},
|
|
[{ type: types.SET_RENDER_TREE_LIST, payload: true }],
|
|
[],
|
|
);
|
|
});
|
|
|
|
it('sets localStorage', () => {
|
|
diffActions.setRenderTreeList({ commit() {} }, { renderTreeList: true });
|
|
|
|
expect(localStorage.setItem).toHaveBeenCalledWith('mr_diff_tree_list', true);
|
|
});
|
|
});
|
|
|
|
describe('setShowWhitespace', () => {
|
|
const endpointUpdateUser = 'user/prefs';
|
|
let putSpy;
|
|
|
|
beforeEach(() => {
|
|
putSpy = jest.spyOn(axios, 'put');
|
|
|
|
mock.onPut(endpointUpdateUser).reply(HTTP_STATUS_OK, {});
|
|
jest.spyOn(eventHub, '$emit').mockImplementation();
|
|
});
|
|
|
|
it('commits SET_SHOW_WHITESPACE', () => {
|
|
return testAction(
|
|
diffActions.setShowWhitespace,
|
|
{ showWhitespace: true, updateDatabase: false },
|
|
{},
|
|
[{ type: types.SET_SHOW_WHITESPACE, payload: true }],
|
|
[],
|
|
);
|
|
});
|
|
|
|
it('saves to the database when the user is logged in', async () => {
|
|
window.gon = { current_user_id: 12345 };
|
|
|
|
await diffActions.setShowWhitespace(
|
|
{ state: { endpointUpdateUser }, commit() {} },
|
|
{ showWhitespace: true, updateDatabase: true },
|
|
);
|
|
|
|
expect(putSpy).toHaveBeenCalledWith(endpointUpdateUser, { show_whitespace_in_diffs: true });
|
|
});
|
|
|
|
it('does not try to save to the API if the user is not logged in', async () => {
|
|
window.gon = {};
|
|
|
|
await diffActions.setShowWhitespace(
|
|
{ state: { endpointUpdateUser }, commit() {} },
|
|
{ showWhitespace: true, updateDatabase: true },
|
|
);
|
|
|
|
expect(putSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('emits eventHub event', async () => {
|
|
await diffActions.setShowWhitespace(
|
|
{ state: {}, commit() {} },
|
|
{ showWhitespace: true, updateDatabase: false },
|
|
);
|
|
|
|
expect(eventHub.$emit).toHaveBeenCalledWith('refetchDiffData');
|
|
});
|
|
});
|
|
|
|
describe('setRenderIt', () => {
|
|
it('commits RENDER_FILE', () => {
|
|
return testAction(
|
|
diffActions.setRenderIt,
|
|
'file',
|
|
{},
|
|
[{ type: types.RENDER_FILE, payload: 'file' }],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('receiveFullDiffError', () => {
|
|
it('updates state with the file that did not load', () => {
|
|
return testAction(
|
|
diffActions.receiveFullDiffError,
|
|
'file',
|
|
{},
|
|
[{ type: types.RECEIVE_FULL_DIFF_ERROR, payload: 'file' }],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('fetchFullDiff', () => {
|
|
describe('success', () => {
|
|
beforeEach(() => {
|
|
mock.onGet(`${TEST_HOST}/context`).replyOnce(HTTP_STATUS_OK, ['test']);
|
|
});
|
|
|
|
it('commits the success and dispatches an action to expand the new lines', () => {
|
|
const file = {
|
|
context_lines_path: `${TEST_HOST}/context`,
|
|
file_path: 'test',
|
|
file_hash: 'test',
|
|
};
|
|
return testAction(
|
|
diffActions.fetchFullDiff,
|
|
file,
|
|
null,
|
|
[{ type: types.RECEIVE_FULL_DIFF_SUCCESS, payload: { filePath: 'test' } }],
|
|
[{ type: 'setExpandedDiffLines', payload: { file, data: ['test'] } }],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('error', () => {
|
|
beforeEach(() => {
|
|
mock.onGet(`${TEST_HOST}/context`).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
|
|
});
|
|
|
|
it('dispatches receiveFullDiffError', () => {
|
|
return testAction(
|
|
diffActions.fetchFullDiff,
|
|
{ context_lines_path: `${TEST_HOST}/context`, file_path: 'test', file_hash: 'test' },
|
|
null,
|
|
[],
|
|
[{ type: 'receiveFullDiffError', payload: 'test' }],
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('toggleFullDiff', () => {
|
|
let state;
|
|
|
|
beforeEach(() => {
|
|
state = {
|
|
diffFiles: [{ file_path: 'test', isShowingFullFile: false }],
|
|
};
|
|
});
|
|
|
|
it('dispatches fetchFullDiff when file is not expanded', () => {
|
|
return testAction(
|
|
diffActions.toggleFullDiff,
|
|
'test',
|
|
state,
|
|
[{ type: types.REQUEST_FULL_DIFF, payload: 'test' }],
|
|
[{ type: 'fetchFullDiff', payload: state.diffFiles[0] }],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('switchToFullDiffFromRenamedFile', () => {
|
|
const SUCCESS_URL = 'fakehost/context.success';
|
|
const testFilePath = 'testpath';
|
|
const updatedViewerName = 'testviewer';
|
|
const preparedLine = { prepared: 'in-a-test' };
|
|
const testFile = {
|
|
file_path: testFilePath,
|
|
file_hash: 'testhash',
|
|
alternate_viewer: { name: updatedViewerName },
|
|
};
|
|
const updatedViewer = {
|
|
name: updatedViewerName,
|
|
automaticallyCollapsed: false,
|
|
manuallyCollapsed: false,
|
|
};
|
|
const testData = [{ rich_text: 'test' }, { rich_text: 'file2' }];
|
|
let renamedFile;
|
|
|
|
beforeEach(() => {
|
|
jest.spyOn(utils, 'prepareLineForRenamedFile').mockImplementation(() => preparedLine);
|
|
});
|
|
|
|
afterEach(() => {
|
|
renamedFile = null;
|
|
});
|
|
|
|
describe('success', () => {
|
|
beforeEach(() => {
|
|
renamedFile = { ...testFile, context_lines_path: SUCCESS_URL };
|
|
mock.onGet(SUCCESS_URL).replyOnce(HTTP_STATUS_OK, testData);
|
|
});
|
|
|
|
it.each`
|
|
diffViewType
|
|
${INLINE_DIFF_VIEW_TYPE}
|
|
${PARALLEL_DIFF_VIEW_TYPE}
|
|
`(
|
|
'performs the correct mutations and starts a render queue for view type $diffViewType',
|
|
({ diffViewType }) => {
|
|
return testAction(
|
|
diffActions.switchToFullDiffFromRenamedFile,
|
|
{ diffFile: renamedFile },
|
|
{ diffViewType },
|
|
[
|
|
{
|
|
type: types.SET_DIFF_FILE_VIEWER,
|
|
payload: { filePath: testFilePath, viewer: updatedViewer },
|
|
},
|
|
{
|
|
type: types.SET_CURRENT_VIEW_DIFF_FILE_LINES,
|
|
payload: { filePath: testFilePath, lines: [preparedLine, preparedLine] },
|
|
},
|
|
],
|
|
[{ type: 'startRenderDiffsQueue' }],
|
|
);
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('setFileUserCollapsed', () => {
|
|
it('commits SET_FILE_COLLAPSED', () => {
|
|
return testAction(
|
|
diffActions.setFileCollapsedByUser,
|
|
{ filePath: 'test', collapsed: true },
|
|
null,
|
|
[
|
|
{
|
|
type: types.SET_FILE_COLLAPSED,
|
|
payload: { filePath: 'test', collapsed: true, trigger: 'manual' },
|
|
},
|
|
],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('setExpandedDiffLines', () => {
|
|
beforeEach(() => {
|
|
utils.idleCallback.mockImplementation((cb) => {
|
|
cb({ timeRemaining: () => 50 });
|
|
});
|
|
});
|
|
|
|
it('commits SET_CURRENT_VIEW_DIFF_FILE_LINES when lines less than MAX_RENDERING_DIFF_LINES', () => {
|
|
utils.convertExpandLines.mockImplementation(() => ['test']);
|
|
|
|
return testAction(
|
|
diffActions.setExpandedDiffLines,
|
|
{ file: { file_path: 'path' }, data: [] },
|
|
{ diffViewType: 'inline' },
|
|
[
|
|
{
|
|
type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES',
|
|
payload: { filePath: 'path', lines: ['test'] },
|
|
},
|
|
],
|
|
[],
|
|
);
|
|
});
|
|
|
|
it('commits ADD_CURRENT_VIEW_DIFF_FILE_LINES when lines more than MAX_RENDERING_DIFF_LINES', () => {
|
|
const lines = new Array(501).fill().map((_, i) => `line-${i}`);
|
|
utils.convertExpandLines.mockReturnValue(lines);
|
|
|
|
return testAction(
|
|
diffActions.setExpandedDiffLines,
|
|
{ file: { file_path: 'path' }, data: [] },
|
|
{ diffViewType: 'inline' },
|
|
[
|
|
{
|
|
type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES',
|
|
payload: { filePath: 'path', lines: lines.slice(0, 200) },
|
|
},
|
|
{ type: 'TOGGLE_DIFF_FILE_RENDERING_MORE', payload: 'path' },
|
|
...new Array(301).fill().map((_, i) => ({
|
|
type: 'ADD_CURRENT_VIEW_DIFF_FILE_LINES',
|
|
payload: { filePath: 'path', line: `line-${i + 200}` },
|
|
})),
|
|
{ type: 'TOGGLE_DIFF_FILE_RENDERING_MORE', payload: 'path' },
|
|
],
|
|
[],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('setSuggestPopoverDismissed', () => {
|
|
it('commits SET_SHOW_SUGGEST_POPOVER', async () => {
|
|
const state = { dismissEndpoint: `${TEST_HOST}/-/user_callouts` };
|
|
mock.onPost(state.dismissEndpoint).reply(HTTP_STATUS_OK, {});
|
|
|
|
jest.spyOn(axios, 'post');
|
|
|
|
await testAction(
|
|
diffActions.setSuggestPopoverDismissed,
|
|
null,
|
|
state,
|
|
[{ type: types.SET_SHOW_SUGGEST_POPOVER }],
|
|
[],
|
|
);
|
|
expect(axios.post).toHaveBeenCalledWith(state.dismissEndpoint, {
|
|
feature_name: 'suggest_popover_dismissed',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('changeCurrentCommit', () => {
|
|
it('commits the new commit information and re-requests the diff metadata for the commit', () => {
|
|
return testAction(
|
|
diffActions.changeCurrentCommit,
|
|
{ commitId: 'NEW' },
|
|
{
|
|
commit: {
|
|
id: 'OLD',
|
|
},
|
|
endpoint: 'URL/OLD',
|
|
endpointBatch: 'URL/OLD',
|
|
endpointMetadata: 'URL/OLD',
|
|
},
|
|
[
|
|
{ type: types.SET_DIFF_FILES, payload: [] },
|
|
{
|
|
type: types.SET_BASE_CONFIG,
|
|
payload: {
|
|
commit: {
|
|
id: 'OLD', // Not a typo: the action fired next will overwrite all of the `commit` in state
|
|
},
|
|
endpoint: 'URL/NEW',
|
|
endpointBatch: 'URL/NEW',
|
|
endpointMetadata: 'URL/NEW',
|
|
},
|
|
},
|
|
],
|
|
[{ type: 'fetchDiffFilesMeta' }],
|
|
);
|
|
});
|
|
|
|
it.each`
|
|
commitId | commit | msg
|
|
${undefined} | ${{ id: 'OLD' }} | ${'`commitId` is a required argument'}
|
|
${'NEW'} | ${null} | ${'`state` must already contain a valid `commit`'}
|
|
${undefined} | ${null} | ${'`commitId` is a required argument'}
|
|
`(
|
|
'returns a rejected promise with the error message $msg given `{ "commitId": $commitId, "state.commit": $commit }`',
|
|
({ commitId, commit, msg }) => {
|
|
const err = new Error(msg);
|
|
const actionReturn = testAction(
|
|
diffActions.changeCurrentCommit,
|
|
{ commitId },
|
|
{
|
|
endpoint: 'URL/OLD',
|
|
endpointBatch: 'URL/OLD',
|
|
endpointMetadata: 'URL/OLD',
|
|
commit,
|
|
},
|
|
[],
|
|
[],
|
|
);
|
|
|
|
return expect(actionReturn).rejects.toStrictEqual(err);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('moveToNeighboringCommit', () => {
|
|
it.each`
|
|
direction | expected | currentCommit
|
|
${'next'} | ${'NEXTSHA'} | ${{ next_commit_id: 'NEXTSHA' }}
|
|
${'previous'} | ${'PREVIOUSSHA'} | ${{ prev_commit_id: 'PREVIOUSSHA' }}
|
|
`(
|
|
'for the direction "$direction", dispatches the action to move to the SHA "$expected"',
|
|
({ direction, expected, currentCommit }) => {
|
|
return testAction(
|
|
diffActions.moveToNeighboringCommit,
|
|
{ direction },
|
|
{ commit: currentCommit },
|
|
[],
|
|
[{ type: 'changeCurrentCommit', payload: { commitId: expected } }],
|
|
);
|
|
},
|
|
);
|
|
|
|
it.each`
|
|
direction | diffsAreLoading | currentCommit
|
|
${'next'} | ${false} | ${{ prev_commit_id: 'PREVIOUSSHA' }}
|
|
${'next'} | ${true} | ${{ prev_commit_id: 'PREVIOUSSHA' }}
|
|
${'next'} | ${false} | ${undefined}
|
|
${'previous'} | ${false} | ${{ next_commit_id: 'NEXTSHA' }}
|
|
${'previous'} | ${true} | ${{ next_commit_id: 'NEXTSHA' }}
|
|
${'previous'} | ${false} | ${undefined}
|
|
`(
|
|
'given `{ "isloading": $diffsAreLoading, "commit": $currentCommit }` in state, no actions are dispatched',
|
|
({ direction, diffsAreLoading, currentCommit }) => {
|
|
return testAction(
|
|
diffActions.moveToNeighboringCommit,
|
|
{ direction },
|
|
{ commit: currentCommit, isLoading: diffsAreLoading },
|
|
[],
|
|
[],
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
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 getters = { flatBlobsList: [{ fileHash: '123' }] };
|
|
const rootGetters = {
|
|
getDiscussion: () => ({ diff_file: { file_hash: '123' } }),
|
|
notesById: { 1: { discussion_id: '2' } },
|
|
};
|
|
|
|
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 rootGetters = {
|
|
getDiscussion: () => ({ id: '1' }),
|
|
notesById: { 1: { discussion_id: '2' } },
|
|
};
|
|
|
|
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 getters = { flatBlobsList: [{ fileHash: '123' }] };
|
|
const rootGetters = {
|
|
getDiscussion: () => ({ diff_file: { file_hash: '124' } }),
|
|
notesById: { 1: { discussion_id: '2' } },
|
|
};
|
|
|
|
diffActions.setCurrentDiffFileIdFromNote({ commit, getters, rootGetters }, '1');
|
|
|
|
expect(commit).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('navigateToDiffFileIndex', () => {
|
|
it('commits SET_CURRENT_DIFF_FILE', () => {
|
|
return testAction(
|
|
diffActions.navigateToDiffFileIndex,
|
|
{ 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', () => {
|
|
const updateUserEndpoint = 'user/prefs';
|
|
let putSpy;
|
|
|
|
beforeEach(() => {
|
|
putSpy = jest.spyOn(axios, 'put');
|
|
|
|
mock.onPut(updateUserEndpoint).reply(HTTP_STATUS_OK, {});
|
|
});
|
|
|
|
it.each`
|
|
value
|
|
${true}
|
|
${false}
|
|
`(
|
|
'commits SET_FILE_BY_FILE and persists the File-by-File user preference with the new value $value',
|
|
async ({ value }) => {
|
|
await testAction(
|
|
diffActions.setFileByFile,
|
|
{ fileByFile: value },
|
|
{
|
|
viewDiffsFileByFile: null,
|
|
endpointUpdateUser: updateUserEndpoint,
|
|
},
|
|
[{ type: types.SET_FILE_BY_FILE, payload: value }],
|
|
[],
|
|
);
|
|
|
|
expect(putSpy).toHaveBeenCalledWith(updateUserEndpoint, { view_diffs_file_by_file: value });
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('reviewFile', () => {
|
|
const file = {
|
|
id: '123',
|
|
file_hash: 'xyz',
|
|
file_identifier_hash: 'abc',
|
|
load_collapsed_diff_url: 'gitlab-org/gitlab-test/-/merge_requests/1/diffs',
|
|
};
|
|
it.each`
|
|
reviews | diffFile | reviewed
|
|
${{ abc: ['123', 'hash:xyz'] }} | ${file} | ${true}
|
|
${{}} | ${file} | ${false}
|
|
`(
|
|
'sets reviews ($reviews) to localStorage and state for file $file if it is marked reviewed=$reviewed',
|
|
({ reviews, diffFile, reviewed }) => {
|
|
const commitSpy = jest.fn();
|
|
const getterSpy = jest.fn().mockReturnValue([]);
|
|
|
|
diffActions.reviewFile(
|
|
{
|
|
commit: commitSpy,
|
|
getters: {
|
|
fileReviews: getterSpy,
|
|
},
|
|
state: {
|
|
mrReviews: { abc: ['123'] },
|
|
},
|
|
},
|
|
{
|
|
file: diffFile,
|
|
reviewed,
|
|
},
|
|
);
|
|
|
|
expect(localStorage.setItem).toHaveBeenCalledTimes(1);
|
|
expect(localStorage.setItem).toHaveBeenCalledWith(
|
|
'gitlab-org/gitlab-test/-/merge_requests/1-file-reviews',
|
|
JSON.stringify(reviews),
|
|
);
|
|
expect(commitSpy).toHaveBeenCalledWith(types.SET_MR_FILE_REVIEWS, reviews);
|
|
},
|
|
);
|
|
});
|
|
});
|