Commit 05f5d6e7 authored by Phil Hughes's avatar Phil Hughes

Merge branch '35428-web-ide-button-missing-from-project-pages' into 'master'

Step 2 - Fix Web IDE on projects without merge requests

See merge request gitlab-org/gitlab!24508
parents 48fd4071 a7840407
......@@ -70,7 +70,7 @@ export default {
:title="$options.currentBranchPermissionsTooltip"
>
<span
class="ide-radio-label"
class="ide-option-label"
data-qa-selector="commit_to_current_branch_radio"
v-html="commitToCurrentBranchText"
></span>
......
<script>
import { createNamespacedHelpers } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
const {
mapState: mapCommitState,
mapActions: mapCommitActions,
mapGetters: mapCommitGetters,
} = createNamespacedHelpers('commit');
const { mapActions: mapCommitActions, mapGetters: mapCommitGetters } = createNamespacedHelpers(
'commit',
);
export default {
directives: {
GlTooltip: GlTooltipDirective,
},
computed: {
...mapCommitState(['shouldCreateMR']),
...mapCommitGetters(['shouldHideNewMrOption']),
...mapCommitGetters(['shouldHideNewMrOption', 'shouldDisableNewMrOption', 'shouldCreateMR']),
tooltipText() {
if (this.shouldDisableNewMrOption) {
return s__(
'IDE|This option is disabled because you are not allowed to create merge requests in this project.',
);
}
return '';
},
},
methods: {
...mapCommitActions(['toggleShouldCreateMR']),
......@@ -21,14 +32,19 @@ export default {
<template>
<fieldset v-if="!shouldHideNewMrOption">
<hr class="my-2" />
<label class="mb-0 js-ide-commit-new-mr">
<label
v-gl-tooltip="tooltipText"
class="mb-0 js-ide-commit-new-mr"
:class="{ 'is-disabled': shouldDisableNewMrOption }"
>
<input
:disabled="shouldDisableNewMrOption"
:checked="shouldCreateMR"
type="checkbox"
data-qa-selector="start_new_mr_checkbox"
@change="toggleShouldCreateMR"
/>
<span class="prepend-left-10">
<span class="prepend-left-10 ide-option-label">
{{ __('Start a new merge request') }}
</span>
</label>
......
......@@ -67,7 +67,7 @@ export default {
@change="updateCommitAction($event.target.value)"
/>
<span class="prepend-left-10">
<span v-if="label" class="ide-radio-label"> {{ label }} </span> <slot v-else></slot>
<span v-if="label" class="ide-option-label"> {{ label }} </span> <slot v-else></slot>
</span>
</label>
<div v-if="commitAction === value && showInput" class="ide-commit-new-branch">
......
<script>
import $ from 'jquery';
import { mapGetters } from 'vuex';
import NavForm from './nav_form.vue';
import NavDropdownButton from './nav_dropdown_button.vue';
......@@ -13,6 +14,9 @@ export default {
isVisibleDropdown: false,
};
},
computed: {
...mapGetters(['canReadMergeRequests']),
},
mounted() {
this.addDropdownListeners();
},
......@@ -42,7 +46,9 @@ export default {
<template>
<div ref="dropdown" class="btn-group ide-nav-dropdown dropdown">
<nav-dropdown-button />
<div class="dropdown-menu dropdown-menu-left p-0"><nav-form v-if="isVisibleDropdown" /></div>
<nav-dropdown-button :show-merge-requests="canReadMergeRequests" />
<div class="dropdown-menu dropdown-menu-left p-0">
<nav-form v-if="isVisibleDropdown" :show-merge-requests="canReadMergeRequests" />
</div>
</div>
</template>
......@@ -10,6 +10,13 @@ export default {
Icon,
DropdownButton,
},
props: {
showMergeRequests: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
...mapState(['currentBranchId', 'currentMergeRequestId']),
mergeRequestLabel() {
......@@ -25,10 +32,10 @@ export default {
<template>
<dropdown-button>
<span class="row">
<span class="col-7 text-truncate">
<span class="col-auto text-truncate" :class="{ 'col-7': showMergeRequests }">
<icon :size="16" :aria-label="__('Current Branch')" name="branch" /> {{ branchLabel }}
</span>
<span class="col-5 pl-0 text-truncate">
<span v-if="showMergeRequests" class="col-5 pl-0 text-truncate">
<icon :size="16" :aria-label="__('Merge Request')" name="merge-request" />
{{ mergeRequestLabel }}
</span>
......
......@@ -11,12 +11,19 @@ export default {
BranchesSearchList,
MergeRequestSearchList,
},
props: {
showMergeRequests: {
type: Boolean,
required: false,
default: true,
},
},
};
</script>
<template>
<div class="ide-nav-form p-0">
<tabs stop-propagation>
<tabs v-if="showMergeRequests" stop-propagation>
<tab active>
<template slot="title">
{{ __('Branches') }}
......@@ -30,5 +37,6 @@ export default {
<merge-request-search-list />
</tab>
</tabs>
<branches-search-list v-else />
</div>
</template>
......@@ -8,6 +8,9 @@ export const MAX_BODY_LENGTH = 72;
export const FILE_VIEW_MODE_EDITOR = 'editor';
export const FILE_VIEW_MODE_PREVIEW = 'preview';
export const PERMISSION_CREATE_MR = 'createMergeRequestIn';
export const PERMISSION_READ_MR = 'readMergeRequest';
export const activityBarViews = {
edit: 'ide-tree',
commit: 'commit-section',
......
query getUserPermissions($projectPath: ID!) {
project(fullPath: $projectPath) {
userPermissions {
createMergeRequestIn,
readMergeRequest
}
}
}
import createGqClient, { fetchPolicies } from '~/lib/graphql';
export default createGqClient(
{},
{
fetchPolicy: fetchPolicies.NO_CACHE,
},
);
import axios from '~/lib/utils/axios_utils';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import Api from '~/api';
import getUserPermissions from '../queries/getUserPermissions.query.graphql';
import gqClient from './gql';
const fetchApiProjectData = projectPath => Api.project(projectPath).then(({ data }) => data);
const fetchGqlProjectData = projectPath =>
gqClient
.query({
query: getUserPermissions,
variables: { projectPath },
})
.then(({ data }) => data.project);
export default {
getFileData(endpoint) {
......@@ -47,7 +59,16 @@ export default {
.then(({ data }) => data);
},
getProjectData(namespace, project) {
return Api.project(`${namespace}/${project}`);
const projectPath = `${namespace}/${project}`;
return Promise.all([fetchApiProjectData(projectPath), fetchGqlProjectData(projectPath)]).then(
([apiProjectData, gqlProjectData]) => ({
data: {
...apiProjectData,
...gqlProjectData,
},
}),
);
},
getProjectMergeRequests(projectId, params = {}) {
return Api.projectMergeRequests(projectId, params);
......
......@@ -2,10 +2,17 @@ import flash from '~/flash';
import { __ } from '~/locale';
import service from '../../services';
import * as types from '../mutation_types';
import { activityBarViews } from '../../constants';
import { activityBarViews, PERMISSION_READ_MR } from '../../constants';
export const getMergeRequestsForBranch = ({ commit, state }, { projectId, branchId } = {}) =>
service
export const getMergeRequestsForBranch = (
{ commit, state, getters },
{ projectId, branchId } = {},
) => {
if (!getters.findProjectPermissions(projectId)[PERMISSION_READ_MR]) {
return Promise.resolve();
}
return service
.getProjectMergeRequests(`${projectId}`, {
source_branch: branchId,
source_project_id: state.projects[projectId].id,
......@@ -36,6 +43,7 @@ export const getMergeRequestsForBranch = ({ commit, state }, { projectId, branch
);
throw e;
});
};
export const getMergeRequestData = (
{ commit, dispatch, state },
......
import { getChangesCountForFiles, filePathMatches } from './utils';
import { activityBarViews, packageJsonPath } from '../constants';
import {
activityBarViews,
packageJsonPath,
PERMISSION_READ_MR,
PERMISSION_CREATE_MR,
} from '../constants';
export const activeFile = state => state.openFiles.find(file => file.active) || null;
......@@ -141,5 +146,14 @@ export const getDiffInfo = (state, getters) => path => {
};
};
export const findProjectPermissions = (state, getters) => projectId =>
getters.findProject(projectId)?.userPermissions || {};
export const canReadMergeRequests = (state, getters) =>
Boolean(getters.findProjectPermissions(state.currentProjectId)[PERMISSION_READ_MR]);
export const canCreateMergeRequests = (state, getters) =>
Boolean(getters.findProjectPermissions(state.currentProjectId)[PERMISSION_CREATE_MR]);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -158,7 +158,7 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
commit(rootTypes.SET_LAST_COMMIT_MSG, '', { root: true });
}, 5000);
if (state.shouldCreateMR) {
if (getters.shouldCreateMR) {
const { currentProject } = rootGetters;
const targetBranch = getters.isCreatingNewBranch
? rootState.currentBranchId
......
......@@ -54,5 +54,11 @@ export const shouldHideNewMrOption = (_state, getters, _rootState, rootGetters)
(!rootGetters.hasMergeRequest && rootGetters.isOnDefaultBranch)) &&
rootGetters.canPushToBranch;
export const shouldDisableNewMrOption = (state, getters, rootState, rootGetters) =>
!rootGetters.canCreateMergeRequests;
export const shouldCreateMR = (state, getters) =>
state.shouldCreateMR && !getters.shouldDisableNewMrOption;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -688,7 +688,7 @@ $ide-commit-header-height: 48px;
font-weight: normal;
&.is-disabled {
.ide-radio-label {
.ide-option-label {
text-decoration: line-through;
}
}
......
......@@ -84,14 +84,13 @@
= render 'projects/find_file_link'
- if can_create_mr_from_fork
- if can_collaborate || current_user&.already_forked?(@project)
- if vue_file_list_enabled?
#js-tree-web-ide-link.d-inline-block
- else
= link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
= _('Web IDE')
- else
- elsif can_create_mr_from_fork
= link_to '#modal-confirm-fork', class: 'btn btn-default qa-web-ide-button', data: { target: '#modal-confirm-fork', toggle: 'modal'} do
= _('Web IDE')
= render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path)
......
---
title: Enable Web IDE on projects without Merge Requests
merge_request: 24508
author:
type: fixed
......@@ -10130,6 +10130,9 @@ msgstr ""
msgid "IDE|Successful commit"
msgstr ""
msgid "IDE|This option is disabled because you are not allowed to create merge requests in this project."
msgstr ""
msgid "IDE|This option is disabled because you don't have write permissions for the current branch."
msgstr ""
......
......@@ -3,14 +3,20 @@
require 'spec_helper'
describe 'Projects > Show > Collaboration links', :js do
let(:project) { create(:project, :repository) }
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project, :repository, :public) }
let(:user) { create(:user) }
before do
project.add_developer(user)
sign_in(user)
end
context 'with developer user' do
before do
project.add_developer(user)
end
it 'shows all the expected links' do
visit project_path(project)
......@@ -65,4 +71,28 @@ describe 'Projects > Show > Collaboration links', :js do
expect(page).not_to have_link('Web IDE')
end
end
context "Web IDE link" do
where(:merge_requests_access_level, :user_level, :expect_ide_link) do
::ProjectFeature::DISABLED | :guest | false
::ProjectFeature::DISABLED | :developer | true
::ProjectFeature::PRIVATE | :guest | false
::ProjectFeature::PRIVATE | :developer | true
::ProjectFeature::ENABLED | :guest | true
::ProjectFeature::ENABLED | :developer | true
end
with_them do
before do
project.project_feature.update!({ merge_requests_access_level: merge_requests_access_level })
project.add_user(user, user_level)
visit project_path(project)
end
it "updates Web IDE link" do
expect(page.has_link?('Web IDE')).to be(expect_ide_link)
end
end
end
end
......@@ -18,6 +18,7 @@ export const projectData = {
},
mergeRequests: {},
merge_requests_enabled: true,
userPermissions: {},
default_branch: 'master',
};
......
......@@ -2,11 +2,17 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import services from '~/ide/services';
import Api from '~/api';
import gqClient from '~/ide/services/gql';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import getUserPermissions from '~/ide/queries/getUserPermissions.query.graphql';
import { projectData } from '../mock_data';
jest.mock('~/api');
jest.mock('~/ide/services/gql');
const TEST_PROJECT_ID = 'alice/wonderland';
const TEST_NAMESPACE = 'alice';
const TEST_PROJECT = 'wonderland';
const TEST_PROJECT_ID = `${TEST_NAMESPACE}/${TEST_PROJECT}`;
const TEST_BRANCH = 'master-patch-123';
const TEST_COMMIT_SHA = '123456789';
const TEST_FILE_PATH = 'README2.md';
......@@ -111,4 +117,27 @@ describe('IDE services', () => {
},
);
});
describe('getProjectData', () => {
it('combines gql and API requests', () => {
const gqlProjectData = {
userPermissions: {
bogus: true,
},
};
Api.project.mockReturnValue(Promise.resolve({ data: { ...projectData } }));
gqClient.query.mockReturnValue(Promise.resolve({ data: { project: gqlProjectData } }));
return services.getProjectData(TEST_NAMESPACE, TEST_PROJECT).then(response => {
expect(response).toEqual({ data: { ...projectData, ...gqlProjectData } });
expect(Api.project).toHaveBeenCalledWith(TEST_PROJECT_ID);
expect(gqClient.query).toHaveBeenCalledWith({
query: getUserPermissions,
variables: {
projectPath: TEST_PROJECT_ID,
},
});
});
});
});
});
......@@ -2,6 +2,8 @@ import * as getters from '~/ide/stores/getters';
import { createStore } from '~/ide/stores';
import { file } from '../helpers';
const TEST_PROJECT_ID = 'test_project';
describe('IDE store getters', () => {
let localState;
let localStore;
......@@ -398,4 +400,38 @@ describe('IDE store getters', () => {
},
);
});
describe('findProjectPermissions', () => {
it('returns false if project not found', () => {
expect(localStore.getters.findProjectPermissions(TEST_PROJECT_ID)).toEqual({});
});
it('finds permission in given project', () => {
const userPermissions = {
readMergeRequest: true,
createMergeRequestsIn: false,
};
localState.projects[TEST_PROJECT_ID] = { userPermissions };
expect(localStore.getters.findProjectPermissions(TEST_PROJECT_ID)).toBe(userPermissions);
});
});
describe.each`
getterName | permissionKey
${'canReadMergeRequests'} | ${'readMergeRequest'}
${'canCreateMergeRequests'} | ${'createMergeRequestIn'}
`('$getterName', ({ getterName, permissionKey }) => {
it.each([true, false])('finds permission for current project (%s)', val => {
localState.projects[TEST_PROJECT_ID] = {
userPermissions: {
[permissionKey]: val,
},
};
localState.currentProjectId = TEST_PROJECT_ID;
expect(localStore.getters[getterName]).toBe(val);
});
});
});
import Vue from 'vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { projectData, branches } from 'spec/ide/mock_data';
import { resetStore } from 'spec/ide/helpers';
import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue';
import store from '~/ide/stores';
import consts from '../../../../../app/assets/javascripts/ide/stores/modules/commit/constants';
import { createStore } from '~/ide/stores';
import { PERMISSION_CREATE_MR } from '~/ide/constants';
import consts from '~/ide/stores/modules/commit/constants';
describe('create new MR checkbox', () => {
let store;
let vm;
const setMR = () => {
vm.$store.state.currentMergeRequestId = '1';
vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
......@@ -15,6 +17,10 @@ describe('create new MR checkbox', () => {
] = { foo: 'bar' };
};
const setPermissions = permissions => {
store.state.projects[store.state.currentProjectId].userPermissions = permissions;
};
const createComponent = ({ currentBranchId = 'master', createNewBranch = false } = {}) => {
const Component = Vue.extend(NewMergeRequestOption);
......@@ -25,20 +31,29 @@ describe('create new MR checkbox', () => {
: consts.COMMIT_TO_CURRENT_BRANCH;
vm.$store.state.currentBranchId = currentBranchId;
vm.$store.state.currentProjectId = 'abcproject';
const proj = JSON.parse(JSON.stringify(projectData));
proj.branches[currentBranchId] = branches.find(branch => branch.name === currentBranchId);
Vue.set(vm.$store.state.projects, 'abcproject', proj);
store.state.projects.abcproject.branches[currentBranchId] = branches.find(
branch => branch.name === currentBranchId,
);
return vm.$mount();
};
const findInput = () => vm.$el.querySelector('input[type="checkbox"]');
const findLabel = () => vm.$el.querySelector('.js-ide-commit-new-mr');
beforeEach(() => {
store = createStore();
store.state.currentProjectId = 'abcproject';
const proj = JSON.parse(JSON.stringify(projectData));
proj.userPermissions[PERMISSION_CREATE_MR] = true;
Vue.set(store.state.projects, 'abcproject', proj);
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
describe('for default branch', () => {
......@@ -160,6 +175,24 @@ describe('create new MR checkbox', () => {
.then(done)
.catch(done.fail);
});
it('shows enablded checkbox', () => {
expect(findLabel().classList.contains('is-disabled')).toBe(false);
expect(findInput().disabled).toBe(false);
});
});
describe('when user cannot create MR', () => {
beforeEach(() => {
setPermissions({ [PERMISSION_CREATE_MR]: false });
createComponent({ currentBranchId: 'regular' });
});
it('disabled checkbox', () => {
expect(findLabel().classList.contains('is-disabled')).toBe(true);
expect(findInput().disabled).toBe(true);
});
});
it('dispatches toggleShouldCreateMR when clicking checkbox', () => {
......
......@@ -2,25 +2,34 @@ import Vue from 'vue';
import { trimText } from 'spec/helpers/text_helper';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import NavDropdownButton from '~/ide/components/nav_dropdown_button.vue';
import store from '~/ide/stores';
import { resetStore } from '../helpers';
import { createStore } from '~/ide/stores';
describe('NavDropdown', () => {
const TEST_BRANCH_ID = 'lorem-ipsum-dolar';
const TEST_MR_ID = '12345';
const Component = Vue.extend(NavDropdownButton);
let store;
let vm;
beforeEach(() => {
vm = mountComponentWithStore(Component, { store });
vm.$mount();
store = createStore();
});
afterEach(() => {
vm.$destroy();
});
const createComponent = (props = {}) => {
vm = mountComponentWithStore(Vue.extend(NavDropdownButton), { props, store });
vm.$mount();
};
resetStore(store);
const findIcon = name => vm.$el.querySelector(`.ic-${name}`);
const findMRIcon = () => findIcon('merge-request');
const findBranchIcon = () => findIcon('branch');
describe('normal', () => {
beforeEach(() => {
createComponent();
});
it('renders empty placeholders, if state is falsey', () => {
......@@ -60,4 +69,25 @@ describe('NavDropdown', () => {
.then(done)
.catch(done.fail);
});
it('shows icons', () => {
expect(findBranchIcon()).toBeTruthy();
expect(findMRIcon()).toBeTruthy();
});
});
describe('with showMergeRequests false', () => {
beforeEach(() => {
createComponent({ showMergeRequests: false });
});
it('shows single empty placeholder, if state is falsey', () => {
expect(trimText(vm.$el.textContent)).toEqual('-');
});
it('shows only branch icon', () => {
expect(findBranchIcon()).toBeTruthy();
expect(findMRIcon()).toBe(null);
});
});
});
......@@ -3,6 +3,9 @@ import Vue from 'vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import store from '~/ide/stores';
import NavDropdown from '~/ide/components/nav_dropdown.vue';
import { PERMISSION_READ_MR } from '~/ide/constants';
const TEST_PROJECT_ID = 'lorem-ipsum';
describe('IDE NavDropdown', () => {
const Component = Vue.extend(NavDropdown);
......@@ -10,6 +13,12 @@ describe('IDE NavDropdown', () => {
let $dropdown;
beforeEach(() => {
store.state.currentProjectId = TEST_PROJECT_ID;
Vue.set(store.state.projects, TEST_PROJECT_ID, {
userPermissions: {
[PERMISSION_READ_MR]: true,
},
});
vm = mountComponentWithStore(Component, { store });
$dropdown = $(vm.$el);
......@@ -21,6 +30,9 @@ describe('IDE NavDropdown', () => {
vm.$destroy();
});
const findIcon = name => vm.$el.querySelector(`.ic-${name}`);
const findMRIcon = () => findIcon('merge-request');
it('renders nothing initially', () => {
expect(vm.$el).not.toContainElement('.ide-nav-form');
});
......@@ -47,4 +59,22 @@ describe('IDE NavDropdown', () => {
.then(done)
.catch(done.fail);
});
it('renders merge request icon', () => {
expect(findMRIcon()).not.toBeNull();
});
describe('when user cannot read merge requests', () => {
beforeEach(done => {
store.state.projects[TEST_PROJECT_ID].userPermissions = {};
vm.$nextTick()
.then(done)
.catch(done.fail);
});
it('does not render merge requests', () => {
expect(findMRIcon()).toBeNull();
});
});
});
......@@ -8,7 +8,7 @@ import actions, {
openMergeRequest,
} from '~/ide/stores/actions/merge_request';
import service from '~/ide/services';
import { activityBarViews } from '~/ide/constants';
import { activityBarViews, PERMISSION_READ_MR } from '~/ide/constants';
import { resetStore } from '../../helpers';
const TEST_PROJECT = 'abcproject';
......@@ -23,6 +23,9 @@ describe('IDE store merge request actions', () => {
store.state.projects[TEST_PROJECT] = {
id: TEST_PROJECT_ID,
mergeRequests: {},
userPermissions: {
[PERMISSION_READ_MR]: true,
},
};
});
......@@ -79,6 +82,19 @@ describe('IDE store merge request actions', () => {
})
.catch(done.fail);
});
it('does nothing if user cannot read MRs', done => {
store.state.projects[TEST_PROJECT].userPermissions[PERMISSION_READ_MR] = false;
store
.dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' })
.then(() => {
expect(service.getProjectMergeRequests).not.toHaveBeenCalled();
expect(store.state.currentMergeRequestId).toBe('');
})
.then(done)
.catch(done.fail);
});
});
describe('no merge requests for branch available case', () => {
......
......@@ -7,7 +7,7 @@ import eventHub from '~/ide/eventhub';
import consts from '~/ide/stores/modules/commit/constants';
import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types';
import * as actions from '~/ide/stores/modules/commit/actions';
import { commitActionTypes } from '~/ide/constants';
import { commitActionTypes, PERMISSION_CREATE_MR } from '~/ide/constants';
import testAction from '../../../../helpers/vuex_action_helper';
const TEST_COMMIT_SHA = '123456789';
......@@ -313,6 +313,9 @@ describe('IDE commit module actions', () => {
},
},
},
userPermissions: {
[PERMISSION_CREATE_MR]: true,
},
},
},
});
......
......@@ -19,12 +19,12 @@ describe 'projects/tree/_tree_header' do
allow(view).to receive(:can_collaborate_with_project?) { true }
end
it 'does not render the WebIDE button when user cannot create fork or cannot open MR' do
it 'renders the WebIDE button when user can collaborate but not create fork or MR' do
allow(view).to receive(:can?) { false }
render
expect(rendered).not_to have_link('Web IDE')
expect(rendered).to have_link('Web IDE')
end
it 'renders the WebIDE button when user can create fork and can open MR in project' do
......@@ -43,4 +43,13 @@ describe 'projects/tree/_tree_header' do
expect(rendered).to have_link('Web IDE', href: '#modal-confirm-fork')
end
it 'does not render the WebIDE button when user cannot collaborate or create mr' do
allow(view).to receive(:can?) { false }
allow(view).to receive(:can_collaborate_with_project?) { false }
render
expect(rendered).not_to have_link('Web IDE')
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment