summaryrefslogtreecommitdiff
path: root/spec/frontend/repository
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/repository')
-rw-r--r--spec/frontend/repository/commits_service_spec.js15
-rw-r--r--spec/frontend/repository/components/blob_content_viewer_spec.js10
-rw-r--r--spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js40
-rw-r--r--spec/frontend/repository/components/blob_viewers/openapi_viewer_spec.js30
-rw-r--r--spec/frontend/repository/components/fork_info_spec.js122
-rw-r--r--spec/frontend/repository/components/new_directory_modal_spec.js8
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js23
-rw-r--r--spec/frontend/repository/components/upload_blob_modal_spec.js4
-rw-r--r--spec/frontend/repository/mock_data.js18
-rw-r--r--spec/frontend/repository/utils/ref_switcher_utils_spec.js7
10 files changed, 258 insertions, 19 deletions
diff --git a/spec/frontend/repository/commits_service_spec.js b/spec/frontend/repository/commits_service_spec.js
index de7c56f239a..e56975d021a 100644
--- a/spec/frontend/repository/commits_service_spec.js
+++ b/spec/frontend/repository/commits_service_spec.js
@@ -1,9 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { loadCommits, isRequested, resetRequestedCommits } from '~/repository/commits_service';
-import httpStatus from '~/lib/utils/http_status';
+import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { createAlert } from '~/flash';
import { I18N_COMMIT_DATA_FETCH_ERROR } from '~/repository/constants';
+import { refWithSpecialCharMock } from './mock_data';
jest.mock('~/flash');
@@ -14,7 +15,7 @@ describe('commits service', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
- mock.onGet(url).reply(httpStatus.OK, [], {});
+ mock.onGet(url).reply(HTTP_STATUS_OK, [], {});
jest.spyOn(axios, 'get');
});
@@ -39,10 +40,12 @@ describe('commits service', () => {
expect(axios.get).toHaveBeenCalledWith(testUrl, { params: { format: 'json', offset } });
});
- it('encodes the path correctly', async () => {
- await requestCommits(1, 'some-project', 'with $peci@l ch@rs/');
+ it('encodes the path and ref', async () => {
+ const encodedRef = encodeURIComponent(refWithSpecialCharMock);
+ const encodedUrl = `/some-project/-/refs/${encodedRef}/logs_tree/with%20%24peci%40l%20ch%40rs%2F`;
+
+ await requestCommits(1, 'some-project', 'with $peci@l ch@rs/', refWithSpecialCharMock);
- const encodedUrl = '/some-project/-/refs/main/logs_tree/with%20%24peci%40l%20ch%40rs%2F';
expect(axios.get).toHaveBeenCalledWith(encodedUrl, expect.anything());
});
@@ -68,7 +71,7 @@ describe('commits service', () => {
it('calls `createAlert` when the request fails', async () => {
const invalidPath = '/#@ some/path';
const invalidUrl = `${url}${invalidPath}`;
- mock.onGet(invalidUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR, [], {});
+ mock.onGet(invalidUrl).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR, [], {});
await requestCommits(1, 'my-project', invalidPath);
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index 6ece72c41bb..2e8860f67ef 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -25,7 +25,7 @@ import CodeIntelligence from '~/code_navigation/components/app.vue';
import * as urlUtility from '~/lib/utils/url_utility';
import { isLoggedIn, handleLocationHash } from '~/lib/utils/common_utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import httpStatusCodes from '~/lib/utils/http_status';
+import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import LineHighlighter from '~/blob/line_highlighter';
import { LEGACY_FILE_TYPES } from '~/repository/constants';
import { SIMPLE_BLOB_VIEWER, RICH_BLOB_VIEWER } from '~/blob/components/constants';
@@ -256,19 +256,19 @@ describe('Blob content viewer component', () => {
);
it('loads the LineHighlighter', async () => {
- mockAxios.onGet(legacyViewerUrl).replyOnce(httpStatusCodes.OK, 'test');
+ mockAxios.onGet(legacyViewerUrl).replyOnce(HTTP_STATUS_OK, 'test');
await createComponent({ blob: { ...simpleViewerMock, fileType, highlightJs } });
expect(LineHighlighter).toHaveBeenCalled();
});
it('does not load the LineHighlighter for RichViewers', async () => {
- mockAxios.onGet(legacyViewerUrl).replyOnce(httpStatusCodes.OK, 'test');
+ mockAxios.onGet(legacyViewerUrl).replyOnce(HTTP_STATUS_OK, 'test');
await createComponent({ blob: { ...richViewerMock, fileType, highlightJs } });
expect(LineHighlighter).not.toHaveBeenCalled();
});
it('scrolls to the hash', async () => {
- mockAxios.onGet(legacyViewerUrl).replyOnce(httpStatusCodes.OK, 'test');
+ mockAxios.onGet(legacyViewerUrl).replyOnce(HTTP_STATUS_OK, 'test');
await createComponent({ blob: { ...simpleViewerMock, fileType, highlightJs } });
expect(handleLocationHash).toHaveBeenCalled();
});
@@ -368,7 +368,7 @@ describe('Blob content viewer component', () => {
it('does not load a CodeIntelligence component when no viewers are loaded', async () => {
const url = 'some_file.js?format=json&viewer=rich';
- mockAxios.onGet(url).replyOnce(httpStatusCodes.INTERNAL_SERVER_ERROR);
+ mockAxios.onGet(url).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
await createComponent({ blob: { ...richViewerMock, fileType: 'unknown' } });
expect(findCodeIntelligence().exists()).toBe(false);
diff --git a/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js
new file mode 100644
index 00000000000..51f3d31ec72
--- /dev/null
+++ b/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js
@@ -0,0 +1,40 @@
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import NotebookViewer from '~/repository/components/blob_viewers/notebook_viewer.vue';
+import notebookLoader from '~/blob/notebook';
+
+jest.mock('~/blob/notebook');
+
+describe('Notebook Viewer', () => {
+ let wrapper;
+
+ const ROOT_RELATIVE_PATH = '/some/notebook/';
+ const DEFAULT_BLOB_DATA = { rawPath: `${ROOT_RELATIVE_PATH}file.ipynb` };
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(NotebookViewer, {
+ propsData: { blob: DEFAULT_BLOB_DATA },
+ });
+ };
+
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findNotebookWrapper = () => wrapper.findByTestId('notebook');
+
+ beforeEach(() => createComponent());
+
+ it('calls the notebook loader', () => {
+ expect(notebookLoader).toHaveBeenCalledWith({
+ el: wrapper.vm.$refs.viewer,
+ relativeRawPath: ROOT_RELATIVE_PATH,
+ });
+ });
+
+ it('renders a loading icon component', () => {
+ expect(findLoadingIcon().props('size')).toBe('lg');
+ });
+
+ it('renders the notebook wrapper', () => {
+ expect(findNotebookWrapper().exists()).toBe(true);
+ expect(findNotebookWrapper().attributes('data-endpoint')).toBe(DEFAULT_BLOB_DATA.rawPath);
+ });
+});
diff --git a/spec/frontend/repository/components/blob_viewers/openapi_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/openapi_viewer_spec.js
new file mode 100644
index 00000000000..21994d04076
--- /dev/null
+++ b/spec/frontend/repository/components/blob_viewers/openapi_viewer_spec.js
@@ -0,0 +1,30 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import OpenapiViewer from '~/repository/components/blob_viewers/openapi_viewer.vue';
+import renderOpenApi from '~/blob/openapi';
+
+jest.mock('~/blob/openapi');
+
+describe('OpenAPI Viewer', () => {
+ let wrapper;
+
+ const DEFAULT_BLOB_DATA = { rawPath: 'some/openapi.yml' };
+
+ const createOpenApiViewer = () => {
+ wrapper = shallowMountExtended(OpenapiViewer, {
+ propsData: { blob: DEFAULT_BLOB_DATA },
+ });
+ };
+
+ const findOpenApiViewer = () => wrapper.findByTestId('openapi');
+
+ beforeEach(() => createOpenApiViewer());
+
+ it('calls the openapi render', () => {
+ expect(renderOpenApi).toHaveBeenCalledWith(wrapper.vm.$refs.viewer);
+ });
+
+ it('renders an openapi viewer', () => {
+ expect(findOpenApiViewer().exists()).toBe(true);
+ expect(findOpenApiViewer().attributes('data-endpoint')).toBe(DEFAULT_BLOB_DATA.rawPath);
+ });
+});
diff --git a/spec/frontend/repository/components/fork_info_spec.js b/spec/frontend/repository/components/fork_info_spec.js
new file mode 100644
index 00000000000..c23d5ae5823
--- /dev/null
+++ b/spec/frontend/repository/components/fork_info_spec.js
@@ -0,0 +1,122 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlSkeletonLoader, GlIcon, GlLink } from '@gitlab/ui';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { createAlert } from '~/flash';
+
+import ForkInfo, { i18n } from '~/repository/components/fork_info.vue';
+import forkDetailsQuery from '~/repository/queries/fork_details.query.graphql';
+import { propsForkInfo } from '../mock_data';
+
+jest.mock('~/flash');
+
+describe('ForkInfo component', () => {
+ let wrapper;
+ let mockResolver;
+ const forkInfoError = new Error('Something went wrong');
+
+ Vue.use(VueApollo);
+
+ const createCommitData = ({ ahead = 3, behind = 7 }) => {
+ return {
+ data: {
+ project: { id: '1', forkDetails: { ahead, behind, __typename: 'ForkDetails' } },
+ },
+ };
+ };
+
+ const createComponent = (props = {}, data = {}, isRequestFailed = false) => {
+ mockResolver = isRequestFailed
+ ? jest.fn().mockRejectedValue(forkInfoError)
+ : jest.fn().mockResolvedValue(createCommitData(data));
+
+ wrapper = shallowMountExtended(ForkInfo, {
+ apolloProvider: createMockApollo([[forkDetailsQuery, mockResolver]]),
+ propsData: { ...propsForkInfo, ...props },
+ });
+ return waitForPromises();
+ };
+
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findSkeleton = () => wrapper.findComponent(GlSkeletonLoader);
+ const findIcon = () => wrapper.findComponent(GlIcon);
+ const findDivergenceMessage = () => wrapper.find('.gl-text-secondary');
+ const findInaccessibleMessage = () => wrapper.findByTestId('inaccessible-project');
+ it('displays a skeleton while loading data', async () => {
+ createComponent();
+ expect(findSkeleton().exists()).toBe(true);
+ });
+
+ it('does not display skeleton when data is loaded', async () => {
+ await createComponent();
+ expect(findSkeleton().exists()).toBe(false);
+ });
+
+ it('renders fork icon', async () => {
+ await createComponent();
+ expect(findIcon().exists()).toBe(true);
+ });
+
+ it('queries the data when sourceName is present', async () => {
+ await createComponent();
+ expect(mockResolver).toHaveBeenCalled();
+ });
+
+ it('does not query the data when sourceName is empty', async () => {
+ await createComponent({ sourceName: null });
+ expect(mockResolver).not.toHaveBeenCalled();
+ });
+
+ it('renders inaccessible message when fork source is not available', async () => {
+ await createComponent({ sourceName: '' });
+ const message = findInaccessibleMessage();
+ expect(message.exists()).toBe(true);
+ expect(message.text()).toBe(i18n.inaccessibleProject);
+ });
+
+ it('shows source project name with a link to a repo', async () => {
+ await createComponent();
+ const link = findLink();
+ expect(link.text()).toBe(propsForkInfo.sourceName);
+ expect(link.attributes('href')).toBe(propsForkInfo.sourcePath);
+ });
+
+ it('renders unknown divergence message when divergence is unknown', async () => {
+ await createComponent({}, { ahead: null, behind: null });
+ expect(findDivergenceMessage().text()).toBe(i18n.unknown);
+ });
+
+ it('shows correct divergence message when data is present', async () => {
+ await createComponent();
+ expect(findDivergenceMessage().text()).toMatchInterpolatedText(
+ '7 commits behind, 3 commits ahead of the upstream repository.',
+ );
+ });
+
+ it('renders up to date message when divergence is unknown', async () => {
+ await createComponent({}, { ahead: 0, behind: 0 });
+ expect(findDivergenceMessage().text()).toBe(i18n.upToDate);
+ });
+
+ it('renders commits ahead message', async () => {
+ await createComponent({}, { behind: 0 });
+ expect(findDivergenceMessage().text()).toBe('3 commits ahead of the upstream repository.');
+ });
+
+ it('renders commits behind message', async () => {
+ await createComponent({}, { ahead: 0 });
+
+ expect(findDivergenceMessage().text()).toBe('7 commits behind the upstream repository.');
+ });
+
+ it('renders alert with error message when request fails', async () => {
+ await createComponent({}, {}, true);
+ expect(createAlert).toHaveBeenCalledWith({
+ message: i18n.error,
+ captureError: true,
+ error: forkInfoError,
+ });
+ });
+});
diff --git a/spec/frontend/repository/components/new_directory_modal_spec.js b/spec/frontend/repository/components/new_directory_modal_spec.js
index cf0d48280f4..4e5c9a685c4 100644
--- a/spec/frontend/repository/components/new_directory_modal_spec.js
+++ b/spec/frontend/repository/components/new_directory_modal_spec.js
@@ -5,7 +5,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
-import httpStatusCodes from '~/lib/utils/http_status';
+import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
import NewDirectoryModal from '~/repository/components/new_directory_modal.vue';
@@ -149,7 +149,7 @@ describe('NewDirectoryModal', () => {
originalBranch,
createNewMr,
} = defaultFormValue;
- mock.onPost(initialProps.path).reply(httpStatusCodes.OK, {});
+ mock.onPost(initialProps.path).reply(HTTP_STATUS_OK, {});
await fillForm();
await submitForm();
@@ -161,7 +161,7 @@ describe('NewDirectoryModal', () => {
});
it('does not submit "create_merge_request" formData if createNewMr is not checked', async () => {
- mock.onPost(initialProps.path).reply(httpStatusCodes.OK, {});
+ mock.onPost(initialProps.path).reply(HTTP_STATUS_OK, {});
await fillForm({ createNewMr: false });
await submitForm();
expect(mock.history.post[0].data.get('create_merge_request')).toBeNull();
@@ -169,7 +169,7 @@ describe('NewDirectoryModal', () => {
it('redirects to the new directory', async () => {
const response = { filePath: 'new-dir-path' };
- mock.onPost(initialProps.path).reply(httpStatusCodes.OK, response);
+ mock.onPost(initialProps.path).reply(HTTP_STATUS_OK, response);
await fillForm({ dirName: 'foo', branchName: 'master', commitMessage: 'foo' });
await submitForm();
diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js
index 6eea66f1a7d..f694c8e9166 100644
--- a/spec/frontend/repository/components/tree_content_spec.js
+++ b/spec/frontend/repository/components/tree_content_spec.js
@@ -5,19 +5,25 @@ import FilePreview from '~/repository/components/preview/index.vue';
import FileTable from '~/repository/components/table/index.vue';
import TreeContent from 'jh_else_ce/repository/components/tree_content.vue';
import { loadCommits, isRequested, resetRequestedCommits } from '~/repository/commits_service';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/flash';
+import { i18n } from '~/repository/constants';
+import { graphQLErrors } from '../mock_data';
jest.mock('~/repository/commits_service', () => ({
loadCommits: jest.fn(() => Promise.resolve()),
isRequested: jest.fn(),
resetRequestedCommits: jest.fn(),
}));
+jest.mock('~/flash');
let vm;
let $apollo;
+const mockResponse = jest.fn().mockReturnValue(Promise.resolve({ data: {} }));
-function factory(path, data = () => ({})) {
+function factory(path, appoloMockResponse = mockResponse) {
$apollo = {
- query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })),
+ query: appoloMockResponse,
};
vm = shallowMount(TreeContent, {
@@ -222,4 +228,17 @@ describe('Repository table component', () => {
expect(loadCommits.mock.calls).toEqual([['', path, '', 0]]);
});
});
+
+ describe('error handling', () => {
+ const gitalyError = { graphQLErrors };
+ it.each`
+ error | message
+ ${gitalyError} | ${i18n.gitalyError}
+ ${'Error'} | ${i18n.generalError}
+ `('should show an expected error', async ({ error, message }) => {
+ factory('/', jest.fn().mockRejectedValue(error));
+ await waitForPromises();
+ expect(createAlert).toHaveBeenCalledWith({ message, captureError: true });
+ });
+ });
});
diff --git a/spec/frontend/repository/components/upload_blob_modal_spec.js b/spec/frontend/repository/components/upload_blob_modal_spec.js
index 8db169b02b4..9de0666f27a 100644
--- a/spec/frontend/repository/components/upload_blob_modal_spec.js
+++ b/spec/frontend/repository/components/upload_blob_modal_spec.js
@@ -5,7 +5,7 @@ import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
-import httpStatusCodes from '~/lib/utils/http_status';
+import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
@@ -158,7 +158,7 @@ describe('UploadBlobModal', () => {
describe('successful response', () => {
beforeEach(async () => {
mock = new MockAdapter(axios);
- mock.onPost(initialProps.path).reply(httpStatusCodes.OK, { filePath: 'blah' });
+ mock.onPost(initialProps.path).reply(HTTP_STATUS_OK, { filePath: 'blah' });
findModal().vm.$emit('primary', mockEvent);
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index cda47a5b0a5..d85434a9148 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -87,6 +87,8 @@ export const applicationInfoMock = { gitpodEnabled: true };
export const propsMock = { path: 'some_file.js', projectPath: 'some/path' };
export const refMock = 'default-ref';
+export const refWithSpecialCharMock = 'feat/selected-#-ref-#';
+export const encodedRefWithSpecialCharMock = 'feat/selected-%23-ref-%23';
export const blobControlsDataMock = {
id: '1234',
@@ -106,3 +108,19 @@ export const blobControlsDataMock = {
},
},
};
+
+export const graphQLErrors = [
+ {
+ message: '14:failed to connect to all addresses.',
+ locations: [{ line: 16, column: 7 }],
+ path: ['project', 'repository', 'paginatedTree'],
+ extensions: { code: 'unavailable', gitaly_code: 14, service: 'git' },
+ },
+];
+
+export const propsForkInfo = {
+ projectPath: 'nataliia/myGitLab',
+ selectedRef: 'main',
+ sourceName: 'gitLab',
+ sourcePath: 'gitlab-org/gitlab',
+};
diff --git a/spec/frontend/repository/utils/ref_switcher_utils_spec.js b/spec/frontend/repository/utils/ref_switcher_utils_spec.js
index 3335059554f..4d0250fffbf 100644
--- a/spec/frontend/repository/utils/ref_switcher_utils_spec.js
+++ b/spec/frontend/repository/utils/ref_switcher_utils_spec.js
@@ -1,5 +1,6 @@
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
import setWindowLocation from 'helpers/set_window_location_helper';
+import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data';
const projectRootPath = 'root/Project1';
const currentRef = 'main';
@@ -19,4 +20,10 @@ describe('generateRefDestinationPath', () => {
setWindowLocation(currentPath);
expect(generateRefDestinationPath(projectRootPath, selectedRef)).toBe(result);
});
+
+ it('encodes the selected ref', () => {
+ const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`;
+
+ expect(generateRefDestinationPath(projectRootPath, refWithSpecialCharMock)).toBe(result);
+ });
});