Merge branch 'fetch-forked-projects-create-mr' into 'master'
Create private merge requests in forks Closes #58583 See merge request gitlab-org/gitlab-ce!29984
This commit is contained in:
commit
a816bad9a4
16 changed files with 599 additions and 15 deletions
|
@ -0,0 +1,58 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
projects: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
selectedProject: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dropdownText() {
|
||||
if (Object.keys(this.selectedProject).length) {
|
||||
return this.selectedProject.name;
|
||||
}
|
||||
|
||||
return __('Select private project');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selectProject(project) {
|
||||
this.$emit('click', project);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-dropdown toggle-class="d-flex align-items-center w-100" class="w-100">
|
||||
<template slot="button-content">
|
||||
<span class="str-truncated-100 mr-2">
|
||||
<icon name="lock" />
|
||||
{{ dropdownText }}
|
||||
</span>
|
||||
<icon name="chevron-down" class="ml-auto" />
|
||||
</template>
|
||||
<gl-dropdown-item v-for="project in projects" :key="project.id" @click="selectProject(project)">
|
||||
<icon
|
||||
name="mobile-issue-close"
|
||||
:class="{ icon: project.id !== selectedProject.id }"
|
||||
class="js-active-project-check"
|
||||
/>
|
||||
<span class="ml-1">{{ project.name }}</span>
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</template>
|
|
@ -0,0 +1,136 @@
|
|||
<script>
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { __, sprintf } from '../../locale';
|
||||
import createFlash from '../../flash';
|
||||
import Api from '../../api';
|
||||
import state from '../state';
|
||||
import Dropdown from './dropdown.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLink,
|
||||
Dropdown,
|
||||
},
|
||||
props: {
|
||||
namespacePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
newForkPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
projects: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedProject() {
|
||||
return state.selectedProject;
|
||||
},
|
||||
noForkText() {
|
||||
return sprintf(
|
||||
__(
|
||||
'To protect this issues confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.',
|
||||
),
|
||||
{ link_start: `<a href="${this.newForkPath}" class="help-link">`, link_end: '</a>' },
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchProjects();
|
||||
this.createBtn = document.querySelector('.js-create-target');
|
||||
this.warningText = document.querySelector('.js-exposed-info-warning');
|
||||
},
|
||||
methods: {
|
||||
selectProject(project) {
|
||||
if (project) {
|
||||
Object.assign(state, {
|
||||
selectedProject: project,
|
||||
});
|
||||
|
||||
if (project.namespaceFullPath !== this.namespacePath) {
|
||||
this.showWarning();
|
||||
}
|
||||
} else if (this.createBtn) {
|
||||
this.createBtn.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
},
|
||||
normalizeProjectData(data) {
|
||||
return data.map(p => ({
|
||||
id: p.id,
|
||||
name: p.name_with_namespace,
|
||||
pathWithNamespace: p.path_with_namespace,
|
||||
namespaceFullpath: p.namespace.full_path,
|
||||
}));
|
||||
},
|
||||
fetchProjects() {
|
||||
Api.projectForks(this.projectPath, {
|
||||
with_merge_requests_enabled: true,
|
||||
min_access_level: 30,
|
||||
visibility: 'private',
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.projects = this.normalizeProjectData(data);
|
||||
this.selectProject(this.projects[0]);
|
||||
})
|
||||
.catch(e => {
|
||||
createFlash(__('Error fetching forked projects. Please try again.'));
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
showWarning() {
|
||||
if (this.warningText) {
|
||||
this.warningText.classList.remove('hidden');
|
||||
}
|
||||
|
||||
if (this.createBtn) {
|
||||
this.createBtn.classList.add('btn-warning');
|
||||
this.createBtn.classList.remove('btn-success');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="form-group">
|
||||
<label>{{ __('Project') }}</label>
|
||||
<div>
|
||||
<dropdown
|
||||
v-if="projects.length"
|
||||
:projects="projects"
|
||||
:selected-project="selectedProject"
|
||||
@click="selectProject"
|
||||
/>
|
||||
<p class="text-muted mt-1 mb-0">
|
||||
<template v-if="projects.length">
|
||||
{{
|
||||
__(
|
||||
'To protect this issues confidentiality, a private fork of this project was selected.',
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ __('No forks available to you.') }}<br />
|
||||
<span v-html="noForkText"></span>
|
||||
</template>
|
||||
<gl-link :href="helpPagePath" class="help-link" target="_blank">
|
||||
<span class="sr-only">{{ __('Read more') }}</span>
|
||||
<i class="fa fa-question-circle" aria-hidden="true"></i>
|
||||
</gl-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
30
app/assets/javascripts/confidential_merge_request/index.js
Normal file
30
app/assets/javascripts/confidential_merge_request/index.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '../lib/utils/common_utils';
|
||||
import ProjectFormGroup from './components/project_form_group.vue';
|
||||
import state from './state';
|
||||
|
||||
export function isConfidentialIssue() {
|
||||
return parseBoolean(document.querySelector('.js-create-mr').dataset.isConfidential);
|
||||
}
|
||||
|
||||
export function canCreateConfidentialMergeRequest() {
|
||||
return isConfidentialIssue() && Object.keys(state.selectedProject).length > 0;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
const el = document.getElementById('js-forked-project');
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
render(h) {
|
||||
return h(ProjectFormGroup, {
|
||||
props: {
|
||||
namespacePath: el.dataset.namespacePath,
|
||||
projectPath: el.dataset.projectPath,
|
||||
newForkPath: el.dataset.newForkPath,
|
||||
helpPagePath: el.dataset.helpPagePath,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
export default Vue.observable({
|
||||
selectedProject: {},
|
||||
});
|
|
@ -5,6 +5,12 @@ import Flash from './flash';
|
|||
import DropLab from './droplab/drop_lab';
|
||||
import ISetter from './droplab/plugins/input_setter';
|
||||
import { __, sprintf } from './locale';
|
||||
import {
|
||||
init as initConfidentialMergeRequest,
|
||||
isConfidentialIssue,
|
||||
canCreateConfidentialMergeRequest,
|
||||
} from './confidential_merge_request';
|
||||
import confidentialMergeRequestState from './confidential_merge_request/state';
|
||||
|
||||
// Todo: Remove this when fixing issue in input_setter plugin
|
||||
const InputSetter = Object.assign({}, ISetter);
|
||||
|
@ -12,6 +18,17 @@ const InputSetter = Object.assign({}, ISetter);
|
|||
const CREATE_MERGE_REQUEST = 'create-mr';
|
||||
const CREATE_BRANCH = 'create-branch';
|
||||
|
||||
function createEndpoint(projectPath, endpoint) {
|
||||
if (canCreateConfidentialMergeRequest()) {
|
||||
return endpoint.replace(
|
||||
projectPath,
|
||||
confidentialMergeRequestState.selectedProject.pathWithNamespace,
|
||||
);
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
export default class CreateMergeRequestDropdown {
|
||||
constructor(wrapperEl) {
|
||||
this.wrapperEl = wrapperEl;
|
||||
|
@ -42,6 +59,8 @@ export default class CreateMergeRequestDropdown {
|
|||
this.refIsValid = true;
|
||||
this.refsPath = this.wrapperEl.dataset.refsPath;
|
||||
this.suggestedRef = this.refInput.value;
|
||||
this.projectPath = this.wrapperEl.dataset.projectPath;
|
||||
this.projectId = this.wrapperEl.dataset.projectId;
|
||||
|
||||
// These regexps are used to replace
|
||||
// a backend generated new branch name and its source (ref)
|
||||
|
@ -58,6 +77,14 @@ export default class CreateMergeRequestDropdown {
|
|||
};
|
||||
|
||||
this.init();
|
||||
|
||||
if (isConfidentialIssue()) {
|
||||
this.createMergeRequestButton.setAttribute(
|
||||
'data-dropdown-trigger',
|
||||
'#create-merge-request-dropdown',
|
||||
);
|
||||
initConfidentialMergeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
available() {
|
||||
|
@ -113,7 +140,9 @@ export default class CreateMergeRequestDropdown {
|
|||
this.isCreatingBranch = true;
|
||||
|
||||
return axios
|
||||
.post(this.createBranchPath)
|
||||
.post(createEndpoint(this.projectPath, this.createBranchPath), {
|
||||
confidential_issue_project_id: canCreateConfidentialMergeRequest() ? this.projectId : null,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.branchCreated = true;
|
||||
window.location.href = data.url;
|
||||
|
@ -125,7 +154,11 @@ export default class CreateMergeRequestDropdown {
|
|||
this.isCreatingMergeRequest = true;
|
||||
|
||||
return axios
|
||||
.post(this.createMrPath)
|
||||
.post(this.createMrPath, {
|
||||
target_project_id: canCreateConfidentialMergeRequest()
|
||||
? confidentialMergeRequestState.selectedProject.id
|
||||
: null,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.mergeRequestCreated = true;
|
||||
window.location.href = data.url;
|
||||
|
@ -149,6 +182,8 @@ export default class CreateMergeRequestDropdown {
|
|||
}
|
||||
|
||||
enable() {
|
||||
if (!canCreateConfidentialMergeRequest()) return;
|
||||
|
||||
this.createMergeRequestButton.classList.remove('disabled');
|
||||
this.createMergeRequestButton.removeAttribute('disabled');
|
||||
|
||||
|
@ -205,7 +240,7 @@ export default class CreateMergeRequestDropdown {
|
|||
if (!ref) return false;
|
||||
|
||||
return axios
|
||||
.get(`${this.refsPath}${encodeURIComponent(ref)}`)
|
||||
.get(`${createEndpoint(this.projectPath, this.refsPath)}${encodeURIComponent(ref)}`)
|
||||
.then(({ data }) => {
|
||||
const branches = data[Object.keys(data)[0]];
|
||||
const tags = data[Object.keys(data)[1]];
|
||||
|
@ -325,6 +360,12 @@ export default class CreateMergeRequestDropdown {
|
|||
let xhr = null;
|
||||
event.preventDefault();
|
||||
|
||||
if (isConfidentialIssue() && !event.target.classList.contains('js-create-target')) {
|
||||
this.droplab.hooks.forEach(hook => hook.list.toggle());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isBusy()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -287,8 +287,8 @@
|
|||
list-style: none;
|
||||
padding: 0 1px;
|
||||
|
||||
a,
|
||||
button,
|
||||
a:not(.help-link),
|
||||
button:not(.btn),
|
||||
.menu-item {
|
||||
@include dropdown-link;
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ class Projects::BranchesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def confidential_issue_project
|
||||
return unless Feature.enabled?(:create_confidential_merge_request, @project)
|
||||
return unless helpers.create_confidential_merge_request_enabled?
|
||||
return if params[:confidential_issue_project_id].blank?
|
||||
|
||||
confidential_issue_project = Project.find(params[:confidential_issue_project_id])
|
||||
|
|
|
@ -172,7 +172,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
def create_merge_request
|
||||
create_params = params.slice(:branch_name, :ref).merge(issue_iid: issue.iid)
|
||||
create_params[:target_project_id] = params[:target_project_id] if Feature.enabled?(:create_confidential_merge_request, @project)
|
||||
create_params[:target_project_id] = params[:target_project_id] if helpers.create_confidential_merge_request_enabled?
|
||||
result = ::MergeRequests::CreateFromIssueService.new(project, current_user, create_params).execute
|
||||
|
||||
if result[:status] == :success
|
||||
|
|
|
@ -137,7 +137,7 @@ module IssuesHelper
|
|||
end
|
||||
|
||||
def create_confidential_merge_request_enabled?
|
||||
Feature.enabled?(:create_confidential_merge_request, @project)
|
||||
Feature.enabled?(:create_confidential_merge_request, @project, default_enabled: true)
|
||||
end
|
||||
|
||||
def show_new_branch_button?
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
|
||||
- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
|
||||
- value = can_create_confidential_merge_request? ? _('Create confidential merge request') : value
|
||||
- create_mr_text = can_create_confidential_merge_request? ? _('Create confidential merge request') : _('Create merge request')
|
||||
|
||||
- can_create_path = can_create_branch_project_issue_path(@project, @issue)
|
||||
- create_mr_path = create_merge_request_project_issue_path(@project, @issue, branch_name: @issue.to_branch_name, ref: @project.default_branch)
|
||||
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
|
||||
- refs_path = refs_namespace_project_path(@project.namespace, @project, search: '')
|
||||
|
||||
.create-mr-dropdown-wrap.d-inline-block.full-width-mobile{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
|
||||
.create-mr-dropdown-wrap.d-inline-block.full-width-mobile.js-create-mr{ data: { project_path: @project.full_path, project_id: @project.id, can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path, is_confidential: can_create_confidential_merge_request?.to_s } }
|
||||
.btn-group.btn-group-sm.unavailable
|
||||
%button.btn.btn-grouped{ type: 'button', disabled: 'disabled' }
|
||||
= icon('spinner', class: 'fa-spin')
|
||||
|
@ -26,7 +27,7 @@
|
|||
.droplab-dropdown
|
||||
%ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-right.gl-show-field-errors{ class: ("create-confidential-merge-request-dropdown-menu" if can_create_confidential_merge_request?), data: { dropdown: true } }
|
||||
- if can_create_merge_request
|
||||
%li.droplab-item-selected{ role: 'button', data: { value: 'create-mr', text: _('Create merge request') } }
|
||||
%li.droplab-item-selected{ role: 'button', data: { value: 'create-mr', text: create_mr_text } }
|
||||
.menu-item
|
||||
= icon('check', class: 'icon')
|
||||
- if can_create_confidential_merge_request?
|
||||
|
@ -41,6 +42,8 @@
|
|||
%li.divider.droplab-item-ignore
|
||||
|
||||
%li.droplab-item-ignore.prepend-left-8.append-right-8.prepend-top-16
|
||||
- if can_create_confidential_merge_request?
|
||||
#js-forked-project{ data: { namespace_path: @project.namespace.full_path, project_path: @project.full_path, new_fork_path: new_project_fork_path(@project), help_page_path: help_page_path('user/project/merge_requests') } }
|
||||
.form-group
|
||||
%label{ for: 'new-branch-name' }
|
||||
= _('Branch name')
|
||||
|
@ -55,4 +58,8 @@
|
|||
|
||||
.form-group
|
||||
%button.btn.btn-success.js-create-target{ type: 'button', data: { action: 'create-mr' } }
|
||||
= _('Create merge request')
|
||||
= create_mr_text
|
||||
|
||||
- if can_create_confidential_merge_request?
|
||||
%p.text-warning.js-exposed-info-warning.hidden
|
||||
= _('This may expose confidential information as the selected fork is in another namespace that can have other members.')
|
||||
|
|
|
@ -4204,6 +4204,9 @@ msgstr ""
|
|||
msgid "Error fetching diverging counts for branches. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Error fetching forked projects. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Error fetching labels."
|
||||
msgstr ""
|
||||
|
||||
|
@ -6869,6 +6872,9 @@ msgstr ""
|
|||
msgid "No files found."
|
||||
msgstr ""
|
||||
|
||||
msgid "No forks available to you."
|
||||
msgstr ""
|
||||
|
||||
msgid "No job trace"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9242,6 +9248,9 @@ msgstr ""
|
|||
msgid "Select members to invite"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select private project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select project"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10815,6 +10824,9 @@ msgstr ""
|
|||
msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
|
||||
msgstr ""
|
||||
|
||||
msgid "This may expose confidential information as the selected fork is in another namespace that can have other members."
|
||||
msgstr ""
|
||||
|
||||
msgid "This means you can not push code until you create an empty repository or import existing one."
|
||||
msgstr ""
|
||||
|
||||
|
@ -11155,6 +11167,12 @@ msgstr ""
|
|||
msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
|
||||
msgstr ""
|
||||
|
||||
msgid "To protect this issues confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private."
|
||||
msgstr ""
|
||||
|
||||
msgid "To protect this issues confidentiality, a private fork of this project was selected."
|
||||
msgstr ""
|
||||
|
||||
msgid "To see all the user's personal access tokens you must impersonate them first."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe 'User creates confidential merge request on issue page', :js do
|
||||
include ProjectForksHelper
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :repository, :public) }
|
||||
let(:issue) { create(:issue, project: project, confidential: true) }
|
||||
|
||||
def visit_confidential_issue
|
||||
sign_in(user)
|
||||
visit project_issue_path(project, issue)
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'user has no private fork' do
|
||||
before do
|
||||
fork_project(project, user, repository: true)
|
||||
visit_confidential_issue
|
||||
end
|
||||
|
||||
it 'shows that user has no fork available' do
|
||||
click_button 'Create confidential merge request'
|
||||
|
||||
page.within '.create-confidential-merge-request-dropdown-menu' do
|
||||
expect(page).to have_content('No forks available to you')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'user has private fork' do
|
||||
let(:forked_project) { fork_project(project, user, repository: true) }
|
||||
|
||||
before do
|
||||
forked_project.update(visibility: Gitlab::VisibilityLevel::PRIVATE)
|
||||
visit_confidential_issue
|
||||
end
|
||||
|
||||
it 'create merge request in fork' do
|
||||
click_button 'Create confidential merge request'
|
||||
|
||||
page.within '.create-confidential-merge-request-dropdown-menu' do
|
||||
expect(page).to have_button(forked_project.name_with_namespace)
|
||||
click_button 'Create confidential merge request'
|
||||
end
|
||||
|
||||
expect(page).to have_content(forked_project.namespace.name)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,101 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Confidential merge request project form group component renders empty state when response is empty 1`] = `
|
||||
<div
|
||||
class="form-group"
|
||||
>
|
||||
<label>
|
||||
Project
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<!---->
|
||||
|
||||
<p
|
||||
class="text-muted mt-1 mb-0"
|
||||
>
|
||||
|
||||
No forks available to you.
|
||||
<br />
|
||||
|
||||
<span>
|
||||
To protect this issues confidentiality,
|
||||
<a
|
||||
class="help-link"
|
||||
href="https://test.com"
|
||||
>
|
||||
fork the project
|
||||
</a>
|
||||
and set the forks visiblity to private.
|
||||
</span>
|
||||
|
||||
<gllink-stub
|
||||
class="help-link"
|
||||
href="/help"
|
||||
target="_blank"
|
||||
>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Read more
|
||||
</span>
|
||||
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-question-circle"
|
||||
/>
|
||||
</gllink-stub>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Confidential merge request project form group component renders fork dropdown 1`] = `
|
||||
<div
|
||||
class="form-group"
|
||||
>
|
||||
<label>
|
||||
Project
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<!---->
|
||||
|
||||
<p
|
||||
class="text-muted mt-1 mb-0"
|
||||
>
|
||||
|
||||
No forks available to you.
|
||||
<br />
|
||||
|
||||
<span>
|
||||
To protect this issues confidentiality,
|
||||
<a
|
||||
class="help-link"
|
||||
href="https://test.com"
|
||||
>
|
||||
fork the project
|
||||
</a>
|
||||
and set the forks visiblity to private.
|
||||
</span>
|
||||
|
||||
<gllink-stub
|
||||
class="help-link"
|
||||
href="/help"
|
||||
target="_blank"
|
||||
>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Read more
|
||||
</span>
|
||||
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-question-circle"
|
||||
/>
|
||||
</gllink-stub>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,56 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlDropdownItem } from '@gitlab/ui';
|
||||
import Dropdown from '~/confidential_merge_request/components/dropdown.vue';
|
||||
|
||||
let vm;
|
||||
|
||||
function factory(projects = []) {
|
||||
vm = mount(Dropdown, {
|
||||
propsData: {
|
||||
projects,
|
||||
selectedProject: projects[0],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Confidential merge request project dropdown component', () => {
|
||||
afterEach(() => {
|
||||
vm.destroy();
|
||||
});
|
||||
|
||||
it('renders dropdown items', () => {
|
||||
factory([
|
||||
{
|
||||
id: 1,
|
||||
name: 'test',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'test',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(vm.findAll(GlDropdownItem).length).toBe(2);
|
||||
});
|
||||
|
||||
it('renders selected project icon', () => {
|
||||
factory([
|
||||
{
|
||||
id: 1,
|
||||
name: 'test',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'test 2',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(vm.find('.js-active-project-check').classes()).not.toContain('icon');
|
||||
expect(
|
||||
vm
|
||||
.findAll('.js-active-project-check')
|
||||
.at(1)
|
||||
.classes(),
|
||||
).toContain('icon');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import ProjectFormGroup from '~/confidential_merge_request/components/project_form_group.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
name_with_namespace: 'root / gitlab-ce',
|
||||
path_with_namespace: 'root/gitlab-ce',
|
||||
namespace: {
|
||||
full_path: 'root',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name_with_namespace: 'test / gitlab-ce',
|
||||
path_with_namespace: 'test/gitlab-ce',
|
||||
namespace: {
|
||||
full_path: 'test',
|
||||
},
|
||||
},
|
||||
];
|
||||
let vm;
|
||||
let mock;
|
||||
|
||||
function factory(projects = mockData) {
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet(/api\/(.*)\/projects\/gitlab-org%2Fgitlab-ce\/forks/).reply(200, projects);
|
||||
|
||||
vm = shallowMount(ProjectFormGroup, {
|
||||
localVue,
|
||||
propsData: {
|
||||
namespacePath: 'gitlab-org',
|
||||
projectPath: 'gitlab-org/gitlab-ce',
|
||||
newForkPath: 'https://test.com',
|
||||
helpPagePath: '/help',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Confidential merge request project form group component', () => {
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
vm.destroy();
|
||||
});
|
||||
|
||||
it('renders fork dropdown', () => {
|
||||
factory();
|
||||
|
||||
return localVue.nextTick(() => {
|
||||
expect(vm.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('sets selected project as first fork', () => {
|
||||
factory();
|
||||
|
||||
return localVue.nextTick(() => {
|
||||
expect(vm.vm.selectedProject).toEqual({
|
||||
id: 1,
|
||||
name: 'root / gitlab-ce',
|
||||
pathWithNamespace: 'root/gitlab-ce',
|
||||
namespaceFullpath: 'root',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders empty state when response is empty', () => {
|
||||
factory([]);
|
||||
|
||||
return localVue.nextTick(() => {
|
||||
expect(vm.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import CreateMergeRequestDropdown from '~/create_merge_request_dropdown';
|
||||
import { TEST_HOST } from 'spec/test_constants';
|
||||
import { TEST_HOST } from './helpers/test_constants';
|
||||
|
||||
describe('CreateMergeRequestDropdown', () => {
|
||||
let axiosMock;
|
||||
|
@ -10,7 +10,7 @@ describe('CreateMergeRequestDropdown', () => {
|
|||
beforeEach(() => {
|
||||
axiosMock = new MockAdapter(axios);
|
||||
|
||||
setFixtures(`
|
||||
document.body.innerHTML = `
|
||||
<div id="dummy-wrapper-element">
|
||||
<div class="available"></div>
|
||||
<div class="unavailable">
|
||||
|
@ -18,11 +18,12 @@ describe('CreateMergeRequestDropdown', () => {
|
|||
<div class="text"></div>
|
||||
</div>
|
||||
<div class="js-ref"></div>
|
||||
<div class="js-create-mr"></div>
|
||||
<div class="js-create-merge-request"></div>
|
||||
<div class="js-create-target"></div>
|
||||
<div class="js-dropdown-toggle"></div>
|
||||
</div>
|
||||
`);
|
||||
`;
|
||||
|
||||
const dummyElement = document.getElementById('dummy-wrapper-element');
|
||||
dropdown = new CreateMergeRequestDropdown(dummyElement);
|
||||
|
@ -36,7 +37,7 @@ describe('CreateMergeRequestDropdown', () => {
|
|||
describe('getRef', () => {
|
||||
it('escapes branch names correctly', done => {
|
||||
const endpoint = `${dropdown.refsPath}contains%23hash`;
|
||||
spyOn(axios, 'get').and.callThrough();
|
||||
jest.spyOn(axios, 'get');
|
||||
axiosMock.onGet(endpoint).replyOnce({});
|
||||
|
||||
dropdown
|
Loading…
Reference in a new issue