diff --git a/app/assets/javascripts/blob/components/blob_header.vue b/app/assets/javascripts/blob/components/blob_header.vue
index 9793c5854651df8ff6b7b9b6f2606bf9b97a42fc..b0011ff365c47e1a71f9c0437daa6dade3273e10 100644
--- a/app/assets/javascripts/blob/components/blob_header.vue
+++ b/app/assets/javascripts/blob/components/blob_header.vue
@@ -38,11 +38,6 @@ export default {
type: Object,
required: true,
},
- isBlobPage: {
- type: Boolean,
- required: false,
- default: false,
- },
hideViewerSwitcher: {
type: Boolean,
required: false,
@@ -178,7 +173,7 @@ export default {
-
+
-
+
diff --git a/app/assets/javascripts/repository/components/header_area/blob_controls.vue b/app/assets/javascripts/repository/components/header_area/blob_controls.vue
index 4c704193e1830b01fdc2f893483d2bfdac9ce864..90ff00bd56b2479f1fb1d2e5db92d7df34bd7238 100644
--- a/app/assets/javascripts/repository/components/header_area/blob_controls.vue
+++ b/app/assets/javascripts/repository/components/header_area/blob_controls.vue
@@ -21,10 +21,15 @@ import { sanitize } from '~/lib/dompurify';
import { InternalEvents } from '~/tracking';
import { FIND_FILE_BUTTON_CLICK } from '~/tracking/constants';
import { updateElementsVisibility } from '~/repository/utils/dom';
+import {
+ showSingleFileEditorForkSuggestion,
+ showWebIdeForkSuggestion,
+} from '~/repository/utils/fork_suggestion_utils';
import blobControlsQuery from '~/repository/queries/blob_controls.query.graphql';
+import userGitpodInfo from '~/repository/queries/user_gitpod_info.query.graphql';
import { getRefType } from '~/repository/utils/ref_type';
import OpenMrBadge from '~/repository/components/header_area/open_mr_badge.vue';
-import { TEXT_FILE_TYPE, DEFAULT_BLOB_INFO } from '../../constants';
+import { TEXT_FILE_TYPE, EMPTY_FILE, DEFAULT_BLOB_INFO } from '../../constants';
import OverflowMenu from './blob_overflow_menu.vue';
export default {
@@ -40,6 +45,7 @@ export default {
OpenMrBadge,
GlButton,
OverflowMenu,
+ WebIdeLink: () => import('ee_else_ce/vue_shared/components/web_ide_link.vue'),
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -63,8 +69,14 @@ export default {
createAlert({ message: this.$options.i18n.errorMessage });
},
},
+ currentUser: {
+ query: userGitpodInfo,
+ error() {
+ createAlert({ message: this.$options.i18n.errorMessage });
+ },
+ },
},
- inject: ['currentRef'],
+ inject: ['currentRef', 'gitpodEnabled'],
provide() {
return {
blobInfo: computed(() => this.blobInfo ?? DEFAULT_BLOB_INFO.repository.blobs.nodes[0]),
@@ -76,6 +88,10 @@ export default {
type: String,
required: true,
},
+ projectIdAsNumber: {
+ type: Number,
+ required: true,
+ },
refType: {
type: String,
required: false,
@@ -90,6 +106,7 @@ export default {
data() {
return {
project: {},
+ currentUser: {},
};
},
computed: {
@@ -105,6 +122,9 @@ export default {
blobInfo() {
return this.project?.repository?.blobs?.nodes[0] || {};
},
+ userPermissions() {
+ return this.project?.userPermissions || DEFAULT_BLOB_INFO.userPermissions;
+ },
storageInfo() {
const { storedExternally, externalStorage } = this.blobInfo;
return {
@@ -121,7 +141,11 @@ export default {
return this.storageInfo.isLfs;
},
isBinaryFileType() {
- return this.isBinary || this.blobInfo.simpleViewer?.fileType !== TEXT_FILE_TYPE;
+ return (
+ this.isBinary ||
+ (this.blobInfo.simpleViewer?.fileType !== TEXT_FILE_TYPE &&
+ this.blobInfo.simpleViewer?.fileType !== EMPTY_FILE)
+ );
},
rawPath() {
return this.blobInfo.externalStorageUrl || this.blobInfo.rawPath;
@@ -153,6 +177,23 @@ export default {
const description = this.$options.i18n.permalinkTooltip;
return this.formatTooltipWithShortcut(description, this.shortcuts.permalink);
},
+ showWebIdeLink() {
+ return !this.blobInfo.archived && this.blobInfo.editBlobPath;
+ },
+ shouldShowSingleFileEditorForkSuggestion() {
+ return showSingleFileEditorForkSuggestion(
+ this.userPermissions,
+ this.isUsingLfs,
+ this.blobInfo.canModifyBlob,
+ );
+ },
+ shouldShowWebIdeForkSuggestion() {
+ return showWebIdeForkSuggestion(
+ this.userPermissions,
+ this.isUsingLfs,
+ this.blobInfo.canModifyBlobWithWebIde,
+ );
+ },
},
watch: {
showBlobControls(shouldShow) {
@@ -230,10 +271,33 @@ export default {
{{ $options.i18n.permalink }}
+
+
-
+
-
+
@@ -141,7 +144,7 @@ export default {
{
let wrapper;
- let btnGroup;
let buttons;
const blobHash = 'foo-bar';
@@ -20,6 +19,9 @@ describe('Blob Header Default Actions', () => {
wrapper = shallowMountExtended(BlobHeaderActions, {
provide: {
blobHash,
+ glFeatures: {
+ blobOverflowMenu: false,
+ },
...provided,
},
propsData: {
@@ -31,17 +33,17 @@ describe('Blob Header Default Actions', () => {
beforeEach(() => {
createComponent();
- btnGroup = wrapper.findComponent(GlButtonGroup);
buttons = wrapper.findAllComponents(GlButton);
});
describe('renders', () => {
+ const findButtonGroup = () => wrapper.findComponent(GlButtonGroup);
const findCopyButton = () => wrapper.findByTestId('copy-contents-button');
const findViewRawButton = () => wrapper.findByTestId('viewRawButton');
const findDownloadButton = () => wrapper.findByTestId('download-button');
it('gl-button-group component', () => {
- expect(btnGroup.exists()).toBe(true);
+ expect(findButtonGroup().exists()).toBe(true);
});
it('exactly 3 buttons with predefined actions', () => {
@@ -133,4 +135,14 @@ describe('Blob Header Default Actions', () => {
expect(findEnvironmentButton().props('icon')).toBe('external-link');
});
});
+
+ describe('when blob_overflow_menu is enabled', () => {
+ it('hides default actions for mobile layout', () => {
+ createComponent({}, { glFeatures: { blobOverflowMenu: true } });
+
+ expect(wrapper.findComponent(GlButtonGroup).attributes('class')).toBe(
+ 'gl-hidden sm:gl-inline-flex',
+ );
+ });
+ });
});
diff --git a/spec/frontend/blob/components/blob_header_spec.js b/spec/frontend/blob/components/blob_header_spec.js
index 70f0b3c315a3145b7f7f5d54ef0cfcae00cc28ce..a52bf82bc435c8b18fdd4989842f99941408f3dc 100644
--- a/spec/frontend/blob/components/blob_header_spec.js
+++ b/spec/frontend/blob/components/blob_header_spec.js
@@ -73,64 +73,108 @@ describe('Blob Header Default Actions', () => {
}
describe('rendering', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
describe('WebIdeLink component', () => {
- it('renders the WebIdeLink component with the correct props', async () => {
- const { ideEditPath, editBlobPath, gitpodBlobUrl, pipelineEditorPath } = Blob;
- const showForkSuggestion = false;
- const showWebIdeForkSuggestion = false;
- await createComponent({ propsData: { showForkSuggestion, showWebIdeForkSuggestion } });
-
- expect(findWebIdeLink().props()).toMatchObject({
- showEditButton: true,
- buttonVariant: 'confirm',
- editUrl: editBlobPath,
- webIdeUrl: ideEditPath,
- needsToFork: showForkSuggestion,
- needsToForkWithWebIde: showWebIdeForkSuggestion,
- showPipelineEditorButton: Boolean(pipelineEditorPath),
- pipelineEditorUrl: pipelineEditorPath,
- gitpodUrl: gitpodBlobUrl,
- showGitpodButton: applicationInfoMock.gitpodEnabled,
- gitpodEnabled: userInfoMock.currentUser.gitpodEnabled,
- });
+ it('does not render WebIdeLink component', () => {
+ expect(findWebIdeLink().exists()).toBe(false);
});
- it('passes the edit button variant down to the WebIdeLink', () => {
- const editButtonVariant = 'danger';
+ describe('when blob_overflow_menu feature flag is false', () => {
+ it('renders the WebIdeLink component with the correct props', async () => {
+ const { ideEditPath, editBlobPath, gitpodBlobUrl, pipelineEditorPath } = Blob;
+ const showForkSuggestion = false;
+ const showWebIdeForkSuggestion = false;
+ await createComponent({
+ options: {
+ provide: {
+ glFeatures: { blobOverflowMenu: false },
+ },
+ },
+ propsData: { showForkSuggestion, showWebIdeForkSuggestion },
+ });
+
+ expect(findWebIdeLink().props()).toMatchObject({
+ showEditButton: true,
+ buttonVariant: 'confirm',
+ editUrl: editBlobPath,
+ webIdeUrl: ideEditPath,
+ needsToFork: showForkSuggestion,
+ needsToForkWithWebIde: showWebIdeForkSuggestion,
+ showPipelineEditorButton: Boolean(pipelineEditorPath),
+ pipelineEditorUrl: pipelineEditorPath,
+ gitpodUrl: gitpodBlobUrl,
+ showGitpodButton: applicationInfoMock.gitpodEnabled,
+ gitpodEnabled: userInfoMock.currentUser.gitpodEnabled,
+ });
+ });
- createComponent({ propsData: { editButtonVariant } });
+ it('passes the edit button variant down to the WebIdeLink', () => {
+ const editButtonVariant = 'danger';
- expect(findWebIdeLink().props('buttonVariant')).toBe(editButtonVariant);
- });
+ createComponent({
+ options: {
+ provide: {
+ glFeatures: { blobOverflowMenu: false },
+ },
+ },
+ propsData: { editButtonVariant },
+ });
- it.each([[{ archived: true }], [{ editBlobPath: null }]])(
- 'does not render the WebIdeLink component when blob is archived or does not have an edit path',
- (blobProps) => {
- createComponent({ blobProps });
+ expect(findWebIdeLink().props('buttonVariant')).toBe(editButtonVariant);
+ });
- expect(findWebIdeLink().exists()).toBe(false);
- },
- );
+ it.each([[{ archived: true }], [{ editBlobPath: null }]])(
+ 'does not render the WebIdeLink component when blob is archived or does not have an edit path',
+ (blobProps) => {
+ createComponent({
+ blobProps,
+ options: {
+ provide: {
+ glFeatures: { blobOverflowMenu: false },
+ },
+ },
+ });
+
+ expect(findWebIdeLink().exists()).toBe(false);
+ },
+ );
+ });
});
describe('default render', () => {
it.each`
- findComponent | componentName
- ${findTableContents} | ${'TableContents'}
- ${findViewSwitcher} | ${'ViewSwitcher'}
- ${findDefaultActions} | ${'DefaultActions'}
- ${findBlobFilePath} | ${'BlobFilePath'}
+ findComponent | componentName
+ ${findTableContents} | ${'TableContents'}
+ ${findViewSwitcher} | ${'ViewSwitcher'}
+ ${findBlobFilePath} | ${'BlobFilePath'}
`('renders $componentName component by default', ({ findComponent }) => {
- createComponent();
-
expect(findComponent().exists()).toBe(true);
});
});
- it('does not render DefaultActions when on blob page', () => {
- createComponent({ propsData: { isBlobPage: true } });
+ describe('DefaultActions component', () => {
+ it('renders DefaultActions', () => {
+ expect(findDefaultActions().exists()).toBe(true);
+ });
+
+ it('passes information about render error down to default actions', () => {
+ createComponent({
+ propsData: {
+ hasRenderError: true,
+ },
+ });
- expect(findDefaultActions().exists()).toBe(false);
+ expect(findDefaultActions().props('hasRenderError')).toBe(true);
+ });
+
+ it('passes the correct isBinary value to default actions when viewing a binary file', () => {
+ createComponent({ propsData: { isBinary: true } });
+
+ expect(findDefaultActions().props('isBinary')).toBe(true);
+ });
});
it.each([[{ showBlameToggle: true }], [{ showBlameToggle: false }]])(
@@ -176,21 +220,6 @@ describe('Blob Header Default Actions', () => {
expect(wrapper.text()).toContain(slotContent);
});
- it('passes information about render error down to default actions', () => {
- createComponent({
- propsData: {
- hasRenderError: true,
- },
- });
- expect(findDefaultActions().props('hasRenderError')).toBe(true);
- });
-
- it('passes the correct isBinary value to default actions when viewing a binary file', () => {
- createComponent({ propsData: { isBinary: true } });
-
- expect(findDefaultActions().props('isBinary')).toBe(true);
- });
-
it('passes the `showBlobSize` prop to `blobFilepath`', () => {
const showBlobSize = false;
createComponent({ propsData: { showBlobSize } });
diff --git a/spec/frontend/repository/components/header_area/blob_controls_spec.js b/spec/frontend/repository/components/header_area/blob_controls_spec.js
index dbf76ef42e55c17b154eb4a805c2f7812b79f0a5..f22ee51ab13173a0ec9f367008efb1ee953fd007 100644
--- a/spec/frontend/repository/components/header_area/blob_controls_spec.js
+++ b/spec/frontend/repository/components/header_area/blob_controls_spec.js
@@ -2,89 +2,105 @@ import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import BlobControls from '~/repository/components/header_area/blob_controls.vue';
-import blobControlsQuery from '~/repository/queries/blob_controls.query.graphql';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
-import createRouter from '~/repository/router';
-import { updateElementsVisibility } from '~/repository/utils/dom';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import WebIdeLink from 'ee_else_ce/vue_shared/components/web_ide_link.vue';
import { resetShortcutsForTests } from '~/behaviors/shortcuts';
import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob';
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
+import BlobControls from '~/repository/components/header_area/blob_controls.vue';
+import blobControlsQuery from '~/repository/queries/blob_controls.query.graphql';
+import userGitpodInfo from '~/repository/queries/user_gitpod_info.query.graphql';
+import createRouter from '~/repository/router';
+import { updateElementsVisibility } from '~/repository/utils/dom';
import OverflowMenu from '~/repository/components/header_area/blob_overflow_menu.vue';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import OpenMrBadge from '~/repository/components/header_area/open_mr_badge.vue';
-import { blobControlsDataMock, refMock } from '../../mock_data';
+import { blobControlsDataMock, refMock, currentUserDataMock } from '../../mock_data';
+Vue.use(VueApollo);
jest.mock('~/repository/utils/dom');
jest.mock('~/behaviors/shortcuts/shortcuts_blob');
jest.mock('~/blob/blob_line_permalink_updater');
-let router;
-let wrapper;
-let mockResolver;
-
-const createComponent = async ({
- props = {},
- blobInfoOverrides = {},
- glFeatures = { blobOverflowMenu: false },
- routerOverride = {},
-} = {}) => {
- Vue.use(VueApollo);
-
- const projectPath = 'some/project';
- router = createRouter(projectPath, refMock);
-
- await router.push({
- name: 'blobPathDecoded',
- params: { path: '/some/file.js' },
- ...routerOverride,
- });
+describe('Blob controls component', () => {
+ let router;
+ let wrapper;
+ let fakeApollo;
+
+ const createComponent = async ({
+ props = {},
+ blobInfoOverrides = {},
+ glFeatures = { blobOverflowMenu: false },
+ routerOverride = {},
+ } = {}) => {
+ Vue.use(VueApollo);
+
+ const projectPath = 'some/project';
+ router = createRouter(projectPath, refMock);
+
+ await router.push({
+ name: 'blobPathDecoded',
+ params: { path: '/some/file.js' },
+ ...routerOverride,
+ });
- mockResolver = jest.fn().mockResolvedValue({
- data: {
- project: {
- __typename: 'Project',
- id: '1234',
- repository: {
- __typename: 'Repository',
- empty: blobControlsDataMock.repository.empty,
- blobs: {
- __typename: 'RepositoryBlobConnection',
- nodes: [{ ...blobControlsDataMock.repository.blobs.nodes[0], ...blobInfoOverrides }],
+ const blobControlsMockResolver = jest.fn().mockResolvedValue({
+ data: {
+ project: {
+ ...blobControlsDataMock,
+ repository: {
+ ...blobControlsDataMock.repository,
+ blobs: {
+ ...blobControlsDataMock.repository.blobs,
+ nodes: [{ ...blobControlsDataMock.repository.blobs.nodes[0], ...blobInfoOverrides }],
+ },
},
},
},
- },
- });
+ });
- await resetShortcutsForTests();
+ const currentUserMockResolver = jest
+ .fn()
+ .mockResolvedValue({ data: { currentUser: currentUserDataMock } });
- wrapper = shallowMountExtended(BlobControls, {
- router,
- apolloProvider: createMockApollo([[blobControlsQuery, mockResolver]]),
- provide: {
- glFeatures,
- currentRef: refMock,
- },
- propsData: {
- projectPath,
- isBinary: false,
- refType: 'heads',
- ...props,
- },
- mixins: [{ data: () => ({ ref: refMock }) }, glFeatureFlagMixin()],
- });
+ await resetShortcutsForTests();
- await waitForPromises();
-};
+ fakeApollo = createMockApollo([
+ [blobControlsQuery, blobControlsMockResolver],
+ [userGitpodInfo, currentUserMockResolver],
+ ]);
+
+ wrapper = shallowMountExtended(BlobControls, {
+ router,
+ apolloProvider: fakeApollo,
+ provide: {
+ glFeatures,
+ currentRef: refMock,
+ gitpodEnabled: true,
+ },
+ propsData: {
+ projectPath,
+ projectIdAsNumber: 1,
+ isBinary: false,
+ refType: 'heads',
+ ...props,
+ },
+ mixins: [{ data: () => ({ ref: refMock }) }, glFeatureFlagMixin()],
+ stubs: {
+ WebIdeLink: false,
+ },
+ });
+
+ await waitForPromises();
+ };
-describe('Blob controls component', () => {
const findOpenMrBadge = () => wrapper.findComponent(OpenMrBadge);
const findFindButton = () => wrapper.findByTestId('find');
const findBlameButton = () => wrapper.findByTestId('blame');
const findPermalinkButton = () => wrapper.findByTestId('permalink');
+ const findWebIdeLink = () => wrapper.findComponent(WebIdeLink);
const findOverflowMenu = () => wrapper.findComponent(OverflowMenu);
const { bindInternalEventDocument } = useMockInternalEventsTracking();
@@ -92,18 +108,8 @@ describe('Blob controls component', () => {
await createComponent();
});
- describe('MR badge', () => {
- it('should render the baadge if `filter_blob_path` flag is on', async () => {
- await createComponent({ glFeatures: { filterBlobPath: true } });
- expect(findOpenMrBadge().exists()).toBe(true);
- expect(findOpenMrBadge().props('blobPath')).toBe('/some/file.js');
- expect(findOpenMrBadge().props('projectPath')).toBe('some/project');
- });
-
- it('should not render the baadge if `filter_blob_path` flag is off', async () => {
- await createComponent({ glFeatures: { filterBlobPath: false } });
- expect(findOpenMrBadge().exists()).toBe(false);
- });
+ afterEach(() => {
+ fakeApollo = null;
});
describe('showBlobControls', () => {
@@ -122,6 +128,46 @@ describe('Blob controls component', () => {
});
});
+ it.each`
+ name | path
+ ${'blobPathDecoded'} | ${null}
+ ${'treePathDecoded'} | ${'myFile.js'}
+ `(
+ 'does not render any buttons if router name is $name and router path is $path',
+ async ({ name, path }) => {
+ await router.replace({ name, params: { path } });
+
+ await nextTick();
+
+ expect(findFindButton().exists()).toBe(false);
+ expect(findBlameButton().exists()).toBe(false);
+ expect(findPermalinkButton().exists()).toBe(false);
+ expect(updateElementsVisibility).toHaveBeenCalledWith('.tree-controls', true);
+ },
+ );
+
+ it('loads the ShortcutsBlob', () => {
+ expect(ShortcutsBlob).toHaveBeenCalled();
+ });
+
+ it('loads the BlobLinePermalinkUpdater', () => {
+ expect(BlobLinePermalinkUpdater).toHaveBeenCalled();
+ });
+
+ describe('MR badge', () => {
+ it('should render the badge if `filter_blob_path` flag is on', async () => {
+ await createComponent({ glFeatures: { filterBlobPath: true } });
+ expect(findOpenMrBadge().exists()).toBe(true);
+ expect(findOpenMrBadge().props('blobPath')).toBe('/some/file.js');
+ expect(findOpenMrBadge().props('projectPath')).toBe('some/project');
+ });
+
+ it('should not render the badge if `filter_blob_path` flag is off', async () => {
+ await createComponent({ glFeatures: { filterBlobPath: false } });
+ expect(findOpenMrBadge().exists()).toBe(false);
+ });
+ });
+
describe('FindFile button', () => {
it('renders FindFile button', () => {
expect(findFindButton().exists()).toBe(true);
@@ -175,72 +221,92 @@ describe('Blob controls component', () => {
expect(findPermalinkButton().attributes('href')).toBe('permalink/file.js');
});
- it.each`
- name | path
- ${'blobPathDecoded'} | ${null}
- ${'treePathDecoded'} | ${'myFile.js'}
- `(
- 'does not render any buttons if router name is $name and router path is $path',
- async ({ name, path }) => {
- await router.replace({ name, params: { path } });
-
- await nextTick();
-
- expect(findFindButton().exists()).toBe(false);
- expect(findBlameButton().exists()).toBe(false);
- expect(findPermalinkButton().exists()).toBe(false);
- expect(updateElementsVisibility).toHaveBeenCalledWith('.tree-controls', true);
- },
- );
-
- it('loads the ShortcutsBlob', () => {
- expect(ShortcutsBlob).toHaveBeenCalled();
+ it('does not render WebIdeLink component', () => {
+ expect(findWebIdeLink().exists()).toBe(false);
});
- it('loads the BlobLinePermalinkUpdater', () => {
- expect(BlobLinePermalinkUpdater).toHaveBeenCalled();
- });
-
- describe('BlobOverflow dropdown', () => {
+ describe('when blobOverflowMenu feature flag is true', () => {
beforeEach(async () => {
await createComponent({ glFeatures: { blobOverflowMenu: true } });
});
- it('renders BlobOverflow component with correct props', () => {
- expect(findOverflowMenu().exists()).toBe(true);
- expect(findOverflowMenu().props()).toEqual({
- projectPath: 'some/project',
- isBinary: true,
- overrideCopy: true,
- isEmptyRepository: false,
- isUsingLfs: false,
+ describe('WebIdeLink component', () => {
+ it('renders the WebIdeLink component with the correct props', () => {
+ expect(findWebIdeLink().props()).toMatchObject({
+ showEditButton: false,
+ editUrl: 'edit/blob/path/file.js',
+ webIdeUrl: 'ide/blob/path/file.js',
+ needsToFork: false,
+ needsToForkWithWebIde: false,
+ showPipelineEditorButton: true,
+ pipelineEditorUrl: 'pipeline/editor/path/file.yml',
+ gitpodUrl: 'gitpod/blob/url/file.js',
+ showGitpodButton: true,
+ gitpodEnabled: true,
+ });
});
- });
- it('passes the correct isBinary value to BlobOverflow when viewing a binary file', async () => {
- await createComponent({
- props: {
- isBinary: true,
- },
- blobInfoOverrides: {
- simpleViewer: {
- ...blobControlsDataMock.repository.blobs.nodes[0].simpleViewer,
- fileType: 'podfile',
+ it('does not render WebIdeLink component if file is archived', async () => {
+ await createComponent({
+ blobInfoOverrides: {
+ ...blobControlsDataMock.repository.blobs.nodes[0],
+ archived: true,
},
- },
- glFeatures: {
- blobOverflowMenu: true,
- },
+ glFeatures: { blobOverflowMenu: true },
+ });
+ expect(findWebIdeLink().exists()).toBe(false);
});
- expect(findOverflowMenu().props('isBinary')).toBe(true);
+ it('does not render WebIdeLink component if file is not editable', async () => {
+ await createComponent({
+ blobInfoOverrides: {
+ ...blobControlsDataMock.repository.blobs.nodes[0],
+ editBlobPath: '',
+ },
+ glFeatures: { blobOverflowMenu: true },
+ });
+ expect(findWebIdeLink().exists()).toBe(false);
+ });
});
- it('copies to clipboard raw blob text, when receives copy event', () => {
- jest.spyOn(navigator.clipboard, 'writeText');
- findOverflowMenu().vm.$emit('copy');
+ describe('BlobOverflow dropdown', () => {
+ it('renders BlobOverflow component with correct props', () => {
+ expect(findOverflowMenu().exists()).toBe(true);
+ expect(findOverflowMenu().props()).toEqual({
+ projectPath: 'some/project',
+ isBinaryFileType: true,
+ overrideCopy: true,
+ isEmptyRepository: false,
+ isUsingLfs: false,
+ userPermissions: {
+ __typename: 'ProjectPermissions',
+ createMergeRequestIn: true,
+ downloadCode: true,
+ forkProject: true,
+ pushCode: true,
+ },
+ });
+ });
+
+ it('passes the correct isBinaryFileType value to BlobOverflow when viewing a binary file', async () => {
+ await createComponent({
+ props: {
+ isBinary: true,
+ },
+ glFeatures: {
+ blobOverflowMenu: true,
+ },
+ });
- expect(navigator.clipboard.writeText).toHaveBeenCalledWith('Example raw text content');
+ expect(findOverflowMenu().props('isBinaryFileType')).toBe(true);
+ });
+
+ it('copies to clipboard raw blob text, when receives copy event', () => {
+ jest.spyOn(navigator.clipboard, 'writeText');
+ findOverflowMenu().vm.$emit('copy');
+
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith('Example raw text content');
+ });
});
});
});
diff --git a/spec/frontend/repository/components/header_area/blob_default_actions_group_spec.js b/spec/frontend/repository/components/header_area/blob_default_actions_group_spec.js
index 7d8988b984d1dc94eefff361cd36b2d373cfd009..6b32eefdf04e0fa1c0fee1dd3fadf760b826b7f4 100644
--- a/spec/frontend/repository/components/header_area/blob_default_actions_group_spec.js
+++ b/spec/frontend/repository/components/header_area/blob_default_actions_group_spec.js
@@ -17,7 +17,7 @@ describe('Blob Default Actions Group', () => {
blobHash: mockBlobHash,
activeViewerType: 'simple',
hasRenderError: false,
- isBinary: false,
+ isBinaryFileType: false,
isEmpty: false,
canDownloadCode: true,
overrideCopy: false,
@@ -78,8 +78,8 @@ describe('Blob Default Actions Group', () => {
});
});
- it('does not render the Copy and view Raw button if isBinary is set to true', () => {
- createComponent({ isBinary: true });
+ it('does not render the Copy and view Raw button if isBinaryFileType is set to true', () => {
+ createComponent({ isBinaryFileType: true });
expect(findCopyFileContentItem()).toBeUndefined();
expect(findViewRawItem()).toBeUndefined();
diff --git a/spec/frontend/repository/components/header_area/blob_overflow_menu_spec.js b/spec/frontend/repository/components/header_area/blob_overflow_menu_spec.js
index 9b1ddeaa5ef51724c2233c6f3c2930e2dd758c33..ae5db38ee936edc0f682531fd1811e7fd9cb340e 100644
--- a/spec/frontend/repository/components/header_area/blob_overflow_menu_spec.js
+++ b/spec/frontend/repository/components/header_area/blob_overflow_menu_spec.js
@@ -52,6 +52,7 @@ describe('Blob Overflow Menu', () => {
},
propsData: {
projectPath,
+ userPermissions: blobControlsDataMock.userPermissions,
...propsData,
},
stub: {
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index bbc18d904b4cb34e55a98440711b2a7f70020ad4..b34a7b2e0e34f00e777cf17489fe4ed9b6b85a2e 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -91,6 +91,7 @@ export const encodedRefWithSpecialCharMock = 'feat/selected-%23-ref-%23';
export const blobControlsDataMock = {
__typename: 'Project',
id: '1234',
+ userPermissions: userPermissionsMock,
repository: {
__typename: 'Repository',
empty: false,
@@ -117,6 +118,10 @@ export const blobControlsDataMock = {
canModifyBlob: true,
canModifyBlobWithWebIde: true,
forkAndViewPath: 'fork/view/path',
+ editBlobPath: 'edit/blob/path/file.js',
+ ideEditPath: 'ide/blob/path/file.js',
+ pipelineEditorPath: 'pipeline/editor/path/file.yml',
+ gitpodBlobUrl: 'gitpod/blob/url/file.js',
simpleViewer: {
__typename: 'BlobViewer',
collapsed: false,
@@ -280,3 +285,11 @@ export const headerAppInjected = {
};
export const FILE_SIZE_3MB = 3000000;
+
+export const currentUserDataMock = {
+ __typename: 'User',
+ id: '1234',
+ gitpodEnabled: true,
+ preferencesGitpodPath: 'preferences/gitpod/path',
+ profileEnableGitpodPath: 'profile/enable/gitpod/path',
+};