New file from interface on existing branch
Now you can create a new file and select a target_branch != source_branch. If the file that you want to create already exists on the target branch an error message is shown A glDropdown is used to select and create a new branch instead of a text field.
This commit is contained in:
parent
9ed3db9150
commit
e0fe2834eb
22 changed files with 710 additions and 18 deletions
|
@ -36,7 +36,7 @@
|
|||
this.removeFile(file);
|
||||
});
|
||||
return this.on('sending', function(file, xhr, formData) {
|
||||
formData.append('target_branch', form.find('.js-target-branch').val());
|
||||
formData.append('target_branch', form.find('input[name="target_branch"]').val());
|
||||
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
|
||||
formData.append('commit_message', form.find('.js-commit-message').val());
|
||||
});
|
||||
|
|
88
app/assets/javascripts/blob/create_branch_dropdown.js
Normal file
88
app/assets/javascripts/blob/create_branch_dropdown.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
class CreateBranchDropdown {
|
||||
constructor(el, targetBranchDropdown) {
|
||||
this.targetBranchDropdown = targetBranchDropdown;
|
||||
this.el = el;
|
||||
this.dropdownBack = this.el.closest('.dropdown').querySelector('.dropdown-menu-back');
|
||||
this.cancelButton = this.el.querySelector('.js-cancel-branch-btn');
|
||||
this.newBranchField = this.el.querySelector('#new_branch_name');
|
||||
this.newBranchCreateButton = this.el.querySelector('.js-new-branch-btn');
|
||||
|
||||
this.newBranchCreateButton.setAttribute('disabled', '');
|
||||
|
||||
this.addBindings();
|
||||
this.cleanupWrapper = this.cleanup.bind(this);
|
||||
document.addEventListener('beforeunload', this.cleanupWrapper);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.cleanBindings();
|
||||
document.removeEventListener('beforeunload', this.cleanupWrapper);
|
||||
}
|
||||
|
||||
cleanBindings() {
|
||||
this.newBranchField.removeEventListener('keyup', this.enableBranchCreateButtonWrapper);
|
||||
this.newBranchField.removeEventListener('change', this.enableBranchCreateButtonWrapper);
|
||||
this.newBranchField.removeEventListener('keydown', this.handleNewBranchKeydownWrapper);
|
||||
this.dropdownBack.removeEventListener('click', this.resetFormWrapper);
|
||||
this.cancelButton.removeEventListener('click', this.handleCancelClickWrapper);
|
||||
this.newBranchCreateButton.removeEventListener('click', this.createBranchWrapper);
|
||||
}
|
||||
|
||||
addBindings() {
|
||||
this.enableBranchCreateButtonWrapper = this.enableBranchCreateButton.bind(this);
|
||||
this.handleNewBranchKeydownWrapper = this.handleNewBranchKeydown.bind(this);
|
||||
this.resetFormWrapper = this.resetForm.bind(this);
|
||||
this.handleCancelClickWrapper = this.handleCancelClick.bind(this);
|
||||
this.createBranchWrapper = this.createBranch.bind(this);
|
||||
|
||||
this.newBranchField.addEventListener('keyup', this.enableBranchCreateButtonWrapper);
|
||||
this.newBranchField.addEventListener('change', this.enableBranchCreateButtonWrapper);
|
||||
this.newBranchField.addEventListener('keydown', this.handleNewBranchKeydownWrapper);
|
||||
this.dropdownBack.addEventListener('click', this.resetFormWrapper);
|
||||
this.cancelButton.addEventListener('click', this.handleCancelClickWrapper);
|
||||
this.newBranchCreateButton.addEventListener('click', this.createBranchWrapper);
|
||||
}
|
||||
|
||||
handleCancelClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.resetForm();
|
||||
this.dropdownBack.click();
|
||||
}
|
||||
|
||||
handleNewBranchKeydown(e) {
|
||||
const keyCode = e.which;
|
||||
const ENTER_KEYCODE = 13;
|
||||
if (keyCode === ENTER_KEYCODE) {
|
||||
this.createBranch(e);
|
||||
}
|
||||
}
|
||||
|
||||
enableBranchCreateButton() {
|
||||
if (this.newBranchField.value !== '') {
|
||||
this.newBranchCreateButton.removeAttribute('disabled');
|
||||
} else {
|
||||
this.newBranchCreateButton.setAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
this.newBranchField.value = '';
|
||||
this.enableBranchCreateButtonWrapper();
|
||||
}
|
||||
|
||||
createBranch(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.newBranchCreateButton.getAttribute('disabled') === '') {
|
||||
return;
|
||||
}
|
||||
const newBranchName = this.newBranchField.value;
|
||||
this.targetBranchDropdown.setNewBranch(newBranchName);
|
||||
this.resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.CreateBranchDropdown = CreateBranchDropdown;
|
152
app/assets/javascripts/blob/target_branch_dropdown.js
Normal file
152
app/assets/javascripts/blob/target_branch_dropdown.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
const SELECT_ITEM_MSG = 'Select';
|
||||
|
||||
class TargetBranchDropDown {
|
||||
constructor(dropdown) {
|
||||
this.dropdown = dropdown;
|
||||
this.$dropdown = $(dropdown);
|
||||
this.fieldName = this.dropdown.getAttribute('data-field-name');
|
||||
this.form = this.dropdown.closest('form');
|
||||
this.createDropdown();
|
||||
}
|
||||
|
||||
static bootstrap() {
|
||||
const dropdowns = document.querySelectorAll('.js-project-branches-dropdown');
|
||||
[].forEach.call(dropdowns, dropdown => new TargetBranchDropDown(dropdown));
|
||||
}
|
||||
|
||||
createDropdown() {
|
||||
const self = this;
|
||||
this.$dropdown.glDropdown({
|
||||
selectable: true,
|
||||
filterable: true,
|
||||
search: {
|
||||
fields: ['title'],
|
||||
},
|
||||
data: (term, callback) => $.ajax({
|
||||
url: self.dropdown.getAttribute('data-refs-url'),
|
||||
data: {
|
||||
ref: self.dropdown.getAttribute('data-ref'),
|
||||
show_all: true,
|
||||
},
|
||||
dataType: 'json',
|
||||
}).done(refs => callback(self.dropdownData(refs))),
|
||||
toggleLabel(item, el) {
|
||||
if (el.is('.is-active')) {
|
||||
return item.text;
|
||||
}
|
||||
return SELECT_ITEM_MSG;
|
||||
},
|
||||
clicked(item, el, e) {
|
||||
e.preventDefault();
|
||||
self.onClick.call(self);
|
||||
},
|
||||
fieldName: self.fieldName,
|
||||
});
|
||||
return new gl.CreateBranchDropdown(this.form.querySelector('.dropdown-new-branch'), this);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.enableSubmit();
|
||||
this.$dropdown.trigger('change.branch');
|
||||
}
|
||||
|
||||
enableSubmit() {
|
||||
const submitBtn = this.form.querySelector('[type="submit"]');
|
||||
if (this.branchInput && this.branchInput.value) {
|
||||
submitBtn.removeAttribute('disabled');
|
||||
} else {
|
||||
submitBtn.setAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
|
||||
dropdownData(refs) {
|
||||
const branchList = this.dropdownItems(refs);
|
||||
this.cachedRefs = refs;
|
||||
this.addDefaultBranch(branchList);
|
||||
this.addNewBranch(branchList);
|
||||
return { Branches: branchList };
|
||||
}
|
||||
|
||||
dropdownItems(refs) {
|
||||
return refs.map(this.dropdownItem);
|
||||
}
|
||||
|
||||
dropdownItem(ref) {
|
||||
return { id: ref, text: ref, title: ref };
|
||||
}
|
||||
|
||||
addDefaultBranch(branchList) {
|
||||
// when no branch is selected do nothing
|
||||
if (!this.branchInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const branchInputVal = this.branchInput.value;
|
||||
const currentBranchIndex = this.searchBranch(branchList, branchInputVal);
|
||||
|
||||
if (currentBranchIndex === -1) {
|
||||
this.unshiftBranch(branchList, this.dropdownItem(branchInputVal));
|
||||
}
|
||||
}
|
||||
|
||||
addNewBranch(branchList) {
|
||||
if (this.newBranch) {
|
||||
this.unshiftBranch(branchList, this.newBranch);
|
||||
}
|
||||
}
|
||||
|
||||
searchBranch(branchList, branchName) {
|
||||
return _.findIndex(branchList, el => branchName === el.id);
|
||||
}
|
||||
|
||||
unshiftBranch(branchList, branch) {
|
||||
const branchIndex = this.searchBranch(branchList, branch.id);
|
||||
|
||||
if (branchIndex === -1) {
|
||||
branchList.unshift(branch);
|
||||
}
|
||||
}
|
||||
|
||||
setNewBranch(newBranchName) {
|
||||
this.newBranch = this.dropdownItem(newBranchName);
|
||||
this.refreshData();
|
||||
this.selectBranch(this.searchBranch(this.glDropdown.fullData.Branches, newBranchName));
|
||||
}
|
||||
|
||||
refreshData() {
|
||||
this.glDropdown.fullData = this.dropdownData(this.cachedRefs);
|
||||
this.clearFilter();
|
||||
}
|
||||
|
||||
clearFilter() {
|
||||
// apply an empty filter in order to refresh the data
|
||||
this.glDropdown.filter.filter('');
|
||||
this.dropdown.closest('.dropdown').querySelector('.dropdown-page-one .dropdown-input-field').value = '';
|
||||
}
|
||||
|
||||
selectBranch(index) {
|
||||
const branch = this.dropdown.closest('.dropdown').querySelectorAll('li a')[index];
|
||||
|
||||
if (!branch.classList.contains('is-active')) {
|
||||
branch.click();
|
||||
} else {
|
||||
this.closeDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
closeDropdown() {
|
||||
this.dropdown.closest('.dropdown').querySelector('.dropdown-menu-close').click();
|
||||
}
|
||||
|
||||
get branchInput() {
|
||||
return this.form.querySelector(`input[name="${this.fieldName}"]`);
|
||||
}
|
||||
|
||||
get glDropdown() {
|
||||
return this.$dropdown.data('glDropdown');
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.TargetBranchDropDown = TargetBranchDropDown;
|
|
@ -59,7 +59,7 @@ const UserCallout = require('./user_callout');
|
|||
}
|
||||
|
||||
Dispatcher.prototype.initPageScripts = function() {
|
||||
var page, path, shortcut_handler;
|
||||
var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
|
||||
page = $('body').attr('data-page');
|
||||
if (!page) {
|
||||
return false;
|
||||
|
@ -245,16 +245,36 @@ const UserCallout = require('./user_callout');
|
|||
case 'projects:tree:show':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
new TreeView();
|
||||
gl.TargetBranchDropDown.bootstrap();
|
||||
break;
|
||||
case 'projects:find_file:show':
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:blob:new':
|
||||
gl.TargetBranchDropDown.bootstrap();
|
||||
break;
|
||||
case 'projects:blob:create':
|
||||
gl.TargetBranchDropDown.bootstrap();
|
||||
break;
|
||||
case 'projects:blob:show':
|
||||
gl.TargetBranchDropDown.bootstrap();
|
||||
new LineHighlighter();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
|
||||
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
|
||||
new ShortcutsBlob({
|
||||
skipResetBindings: true,
|
||||
fileBlobPermalinkUrl,
|
||||
});
|
||||
break;
|
||||
case 'projects:blob:edit':
|
||||
gl.TargetBranchDropDown.bootstrap();
|
||||
break;
|
||||
case 'projects:blame:show':
|
||||
new LineHighlighter();
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
|
||||
const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
|
||||
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
|
||||
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
|
||||
new ShortcutsBlob({
|
||||
skipResetBindings: true,
|
||||
fileBlobPermalinkUrl,
|
||||
|
|
|
@ -66,6 +66,8 @@ import './blob/blob_gitignore_selectors';
|
|||
import './blob/blob_license_selector';
|
||||
import './blob/blob_license_selectors';
|
||||
import './blob/template_selector';
|
||||
import './blob/create_branch_dropdown';
|
||||
import './blob/target_branch_dropdown';
|
||||
|
||||
// templates
|
||||
import './templates/issuable_template_selector';
|
||||
|
|
|
@ -3,19 +3,23 @@
|
|||
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
|
||||
|
||||
this.NewCommitForm = (function() {
|
||||
function NewCommitForm(form) {
|
||||
function NewCommitForm(form, targetBranchName = 'target_branch') {
|
||||
this.form = form;
|
||||
this.targetBranchName = targetBranchName;
|
||||
this.renderDestination = bind(this.renderDestination, this);
|
||||
this.newBranch = form.find('.js-target-branch');
|
||||
this.targetBranchDropdown = form.find('button.js-target-branch');
|
||||
this.originalBranch = form.find('.js-original-branch');
|
||||
this.createMergeRequest = form.find('.js-create-merge-request');
|
||||
this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
|
||||
this.targetBranchDropdown.on('change.branch', this.renderDestination);
|
||||
this.renderDestination();
|
||||
this.newBranch.keyup(this.renderDestination);
|
||||
}
|
||||
|
||||
NewCommitForm.prototype.renderDestination = function() {
|
||||
var different;
|
||||
different = this.newBranch.val() !== this.originalBranch.val();
|
||||
var targetBranch = this.form.find(`input[name="${this.targetBranchName}"]`);
|
||||
|
||||
different = targetBranch.val() !== this.originalBranch.val();
|
||||
if (different) {
|
||||
this.createMergeRequestContainer.show();
|
||||
if (!this.wasDifferent) {
|
||||
|
|
|
@ -795,7 +795,8 @@ pre.light-well {
|
|||
}
|
||||
|
||||
.project-refs-form .dropdown-menu,
|
||||
.dropdown-menu-projects {
|
||||
.dropdown-menu-projects,
|
||||
.dropdown-menu-branches {
|
||||
width: 300px;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
|
|
|
@ -23,6 +23,8 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
update_ref
|
||||
|
||||
create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
|
||||
success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) },
|
||||
failure_view: :new,
|
||||
|
@ -87,6 +89,11 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def update_ref
|
||||
branch_exists = @repository.find_branch(@target_branch)
|
||||
@ref = @target_branch if branch_exists
|
||||
end
|
||||
|
||||
def blob
|
||||
@blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path))
|
||||
|
||||
|
|
|
@ -10,15 +10,16 @@ class Projects::BranchesController < Projects::ApplicationController
|
|||
def index
|
||||
@sort = params[:sort].presence || sort_value_name
|
||||
@branches = BranchesFinder.new(@repository, params).execute
|
||||
@branches = Kaminari.paginate_array(@branches).page(params[:page])
|
||||
|
||||
@max_commits = @branches.reduce(0) do |memo, branch|
|
||||
diverging_commit_counts = repository.diverging_commit_counts(branch)
|
||||
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
|
||||
end
|
||||
@branches = Kaminari.paginate_array(@branches).page(params[:page]) unless params[:show_all].present?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.html do
|
||||
@max_commits = @branches.reduce(0) do |memo, branch|
|
||||
diverging_commit_counts = repository.diverging_commit_counts(branch)
|
||||
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
|
||||
end
|
||||
end
|
||||
format.json do
|
||||
render json: @branches.map(&:name)
|
||||
end
|
||||
|
|
|
@ -37,4 +37,4 @@
|
|||
= commit_in_fork_help
|
||||
|
||||
:javascript
|
||||
new NewCommitForm($('.js-#{type}-form'))
|
||||
new NewCommitForm($('.js-#{type}-form'), 'start_branch')
|
||||
|
|
8
app/views/shared/_branch_switcher.html.haml
Normal file
8
app/views/shared/_branch_switcher.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
|||
- dropdown_toggle_text = @target_branch || tree_edit_branch
|
||||
= hidden_field_tag 'target_branch', dropdown_toggle_text
|
||||
|
||||
.dropdown
|
||||
= dropdown_toggle dropdown_toggle_text, { toggle: 'dropdown', selected: dropdown_toggle_text, field_name: 'target_branch', form_id: '.js-edit-blob-form', refs_url: namespace_project_branches_path(@project.namespace, @project) }, { toggle_class: 'js-project-branches-dropdown js-target-branch' }
|
||||
.dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging.dropdown-menu-branches
|
||||
= render partial: 'shared/projects/blob/branch_page_default'
|
||||
= render partial: 'shared/projects/blob/branch_page_create'
|
|
@ -7,7 +7,7 @@
|
|||
.form-group.branch
|
||||
= label_tag 'target_branch', 'Target branch', class: 'control-label'
|
||||
.col-sm-10
|
||||
= text_field_tag 'target_branch', @target_branch || tree_edit_branch, required: true, class: "form-control js-target-branch"
|
||||
= render 'shared/branch_switcher'
|
||||
|
||||
.js-create-merge-request-container
|
||||
.checkbox
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
.dropdown-page-two.dropdown-new-branch
|
||||
= dropdown_title('Create new branch', back: true)
|
||||
= dropdown_content do
|
||||
%input#new_branch_name.default-dropdown-input.append-bottom-10{ type: "text", placeholder: "Name new branch" }
|
||||
%button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" }
|
||||
Create
|
||||
%button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" }
|
||||
Cancel
|
|
@ -0,0 +1,10 @@
|
|||
.dropdown-page-one
|
||||
= dropdown_title "Select branch"
|
||||
= dropdown_filter "Search branches"
|
||||
= dropdown_content
|
||||
= dropdown_loading
|
||||
= dropdown_footer do
|
||||
%ul.dropdown-footer-list
|
||||
%li
|
||||
%a.create-new-branch.dropdown-toggle-page{ href: "#" }
|
||||
Create new branch
|
4
changelogs/unreleased/24501-new-file-existing-branch.yml
Normal file
4
changelogs/unreleased/24501-new-file-existing-branch.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: New file from interface on existing branch
|
||||
merge_request: 8427
|
||||
author: Jacopo Beschi @jacopo-beschi
|
|
@ -82,7 +82,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I fill the new branch name' do
|
||||
fill_in :target_branch, with: 'new_branch_name', visible: true
|
||||
first('button.js-target-branch', visible: true).click
|
||||
first('.create-new-branch', visible: true).click
|
||||
first('#new_branch_name', visible: true).set('new_branch_name')
|
||||
first('.js-new-branch-btn', visible: true).click
|
||||
end
|
||||
|
||||
step 'I fill the new file name with an illegal name' do
|
||||
|
|
|
@ -266,5 +266,19 @@ describe Projects::BranchesController do
|
|||
expect(parsed_response.first).to eq 'master'
|
||||
end
|
||||
end
|
||||
|
||||
context 'show_all = true' do
|
||||
it 'returns all the branches name' do
|
||||
get :index,
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
format: :json,
|
||||
show_all: true
|
||||
|
||||
parsed_response = JSON.parse(response.body)
|
||||
|
||||
expect(parsed_response.length).to eq(project.repository.branches.count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
107
spec/features/projects/blobs/user_create_spec.rb
Normal file
107
spec/features/projects/blobs/user_create_spec.rb
Normal file
|
@ -0,0 +1,107 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'New blob creation', feature: true, js: true do
|
||||
include WaitForAjax
|
||||
|
||||
given(:user) { create(:user) }
|
||||
given(:role) { :developer }
|
||||
given(:project) { create(:project) }
|
||||
given(:content) { 'class NextFeature\nend\n' }
|
||||
|
||||
background do
|
||||
login_as(user)
|
||||
project.team << [user, role]
|
||||
visit namespace_project_new_blob_path(project.namespace, project, 'master')
|
||||
end
|
||||
|
||||
def edit_file
|
||||
wait_for_ajax
|
||||
fill_in 'file_name', with: 'feature.rb'
|
||||
execute_script("ace.edit('editor').setValue('#{content}')")
|
||||
end
|
||||
|
||||
def select_branch_index(index)
|
||||
first('button.js-target-branch').click
|
||||
wait_for_ajax
|
||||
all('a[data-group="Branches"]')[index].click
|
||||
end
|
||||
|
||||
def create_new_branch(name)
|
||||
first('button.js-target-branch').click
|
||||
click_link 'Create new branch'
|
||||
fill_in 'new_branch_name', with: name
|
||||
click_button 'Create'
|
||||
end
|
||||
|
||||
def commit_file
|
||||
click_button 'Commit Changes'
|
||||
end
|
||||
|
||||
context 'with default target branch' do
|
||||
background do
|
||||
edit_file
|
||||
commit_file
|
||||
end
|
||||
|
||||
scenario 'creates the blob in the default branch' do
|
||||
expect(page).to have_content 'master'
|
||||
expect(page).to have_content 'successfully created'
|
||||
expect(page).to have_content 'NextFeature'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with different target branch' do
|
||||
background do
|
||||
edit_file
|
||||
select_branch_index(0)
|
||||
commit_file
|
||||
end
|
||||
|
||||
scenario 'creates the blob in the different branch' do
|
||||
expect(page).to have_content 'test'
|
||||
expect(page).to have_content 'successfully created'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a new target branch' do
|
||||
given(:new_branch_name) { 'new-feature' }
|
||||
|
||||
background do
|
||||
edit_file
|
||||
create_new_branch(new_branch_name)
|
||||
commit_file
|
||||
end
|
||||
|
||||
scenario 'creates the blob in the new branch' do
|
||||
expect(page).to have_content new_branch_name
|
||||
expect(page).to have_content 'successfully created'
|
||||
end
|
||||
scenario 'returns you to the mr' do
|
||||
expect(page).to have_content 'New Merge Request'
|
||||
expect(page).to have_content "From #{new_branch_name} into master"
|
||||
expect(page).to have_content 'Add new file'
|
||||
end
|
||||
end
|
||||
|
||||
context 'the file already exist in the source branch' do
|
||||
background do
|
||||
Files::CreateService.new(
|
||||
project,
|
||||
user,
|
||||
start_branch: 'master',
|
||||
target_branch: 'master',
|
||||
commit_message: 'Create file',
|
||||
file_path: 'feature.rb',
|
||||
file_content: content
|
||||
).execute
|
||||
edit_file
|
||||
commit_file
|
||||
end
|
||||
|
||||
scenario 'shows error message' do
|
||||
expect(page).to have_content('Your changes could not be committed because a file with the same name already exists')
|
||||
expect(page).to have_content('New File')
|
||||
expect(page).to have_content('NextFeature')
|
||||
end
|
||||
end
|
||||
end
|
109
spec/javascripts/blob/create_branch_dropdown_spec.js
Normal file
109
spec/javascripts/blob/create_branch_dropdown_spec.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
require('jquery');
|
||||
require('~/extensions/jquery.js');
|
||||
require('~/gl_dropdown');
|
||||
require('~/lib/utils/type_utility');
|
||||
require('~/blob/create_branch_dropdown');
|
||||
require('~/blob/target_branch_dropdown');
|
||||
|
||||
describe('CreateBranchDropdown', () => {
|
||||
const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
|
||||
// selectors
|
||||
const createBranchSel = '.js-new-branch-btn';
|
||||
const backBtnSel = '.dropdown-menu-back';
|
||||
const cancelBtnSel = '.js-cancel-branch-btn';
|
||||
const branchNameSel = '#new_branch_name';
|
||||
const branchName = 'new_name';
|
||||
let dropdown;
|
||||
|
||||
function createDropdown() {
|
||||
const dropdownEl = document.querySelector('.js-project-branches-dropdown');
|
||||
const projectBranches = getJSONFixture('project_branches.json');
|
||||
dropdown = new gl.TargetBranchDropDown(dropdownEl);
|
||||
dropdown.cachedRefs = projectBranches;
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
function createBranchBtn() {
|
||||
return document.querySelector(createBranchSel);
|
||||
}
|
||||
|
||||
function backBtn() {
|
||||
return document.querySelector(backBtnSel);
|
||||
}
|
||||
|
||||
function cancelBtn() {
|
||||
return document.querySelector(cancelBtnSel);
|
||||
}
|
||||
|
||||
function branchNameEl() {
|
||||
return document.querySelector(branchNameSel);
|
||||
}
|
||||
|
||||
function changeBranchName(text) {
|
||||
branchNameEl().value = text;
|
||||
branchNameEl().dispatchEvent(new Event('change'));
|
||||
}
|
||||
|
||||
preloadFixtures(fixtureTemplate);
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures(fixtureTemplate);
|
||||
createDropdown();
|
||||
});
|
||||
|
||||
it('disable submit when branch name is empty', () => {
|
||||
expect(createBranchBtn()).toBeDisabled();
|
||||
});
|
||||
|
||||
it('enable submit when branch name is present', () => {
|
||||
changeBranchName(branchName);
|
||||
|
||||
expect(createBranchBtn()).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('resets the form when cancel btn is clicked and triggers dropdownback', () => {
|
||||
const spyBackEvent = spyOnEvent(backBtnSel, 'click');
|
||||
changeBranchName(branchName);
|
||||
|
||||
cancelBtn().click();
|
||||
|
||||
expect(branchNameEl()).toHaveValue('');
|
||||
expect(spyBackEvent).toHaveBeenTriggered();
|
||||
});
|
||||
|
||||
it('resets the form when back btn is clicked', () => {
|
||||
changeBranchName(branchName);
|
||||
|
||||
backBtn().click();
|
||||
|
||||
expect(branchNameEl()).toHaveValue('');
|
||||
});
|
||||
|
||||
describe('new branch creation', () => {
|
||||
beforeEach(() => {
|
||||
changeBranchName(branchName);
|
||||
});
|
||||
it('sets the new branch name and updates the dropdown', () => {
|
||||
spyOn(dropdown, 'setNewBranch');
|
||||
|
||||
createBranchBtn().click();
|
||||
|
||||
expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName);
|
||||
});
|
||||
|
||||
it('resets the form', () => {
|
||||
createBranchBtn().click();
|
||||
|
||||
expect(branchNameEl()).toHaveValue('');
|
||||
});
|
||||
|
||||
it('is triggered with enter keypress', () => {
|
||||
spyOn(dropdown, 'setNewBranch');
|
||||
const enterEvent = new Event('keydown');
|
||||
enterEvent.which = 13;
|
||||
branchNameEl().dispatchEvent(enterEvent);
|
||||
|
||||
expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName);
|
||||
});
|
||||
});
|
||||
});
|
121
spec/javascripts/blob/target_branch_dropdown_spec.js
Normal file
121
spec/javascripts/blob/target_branch_dropdown_spec.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
require('jquery');
|
||||
require('~/extensions/jquery.js');
|
||||
require('~/gl_dropdown');
|
||||
require('~/lib/utils/type_utility');
|
||||
require('~/blob/create_branch_dropdown');
|
||||
require('~/blob/target_branch_dropdown');
|
||||
|
||||
describe('TargetBranchDropdown', () => {
|
||||
const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
|
||||
let dropdown;
|
||||
|
||||
function createDropdown() {
|
||||
const projectBranches = getJSONFixture('project_branches.json');
|
||||
const dropdownEl = document.querySelector('.js-project-branches-dropdown');
|
||||
dropdown = new gl.TargetBranchDropDown(dropdownEl);
|
||||
dropdown.cachedRefs = projectBranches;
|
||||
dropdown.refreshData();
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
function submitBtn() {
|
||||
return document.querySelector('button[type="submit"]');
|
||||
}
|
||||
|
||||
function searchField() {
|
||||
return document.querySelector('.dropdown-page-one .dropdown-input-field');
|
||||
}
|
||||
|
||||
function element() {
|
||||
return document.querySelectorAll('div.dropdown-content li a');
|
||||
}
|
||||
|
||||
function elementAtIndex(index) {
|
||||
return element()[index];
|
||||
}
|
||||
|
||||
function clickElementAtIndex(index) {
|
||||
elementAtIndex(index).click();
|
||||
}
|
||||
|
||||
preloadFixtures(fixtureTemplate);
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures(fixtureTemplate);
|
||||
createDropdown();
|
||||
});
|
||||
|
||||
it('disable submit when branch is not selected', () => {
|
||||
document.querySelector('input[name="target_branch"]').value = null;
|
||||
clickElementAtIndex(1);
|
||||
|
||||
expect(submitBtn().getAttribute('disabled')).toEqual('');
|
||||
});
|
||||
|
||||
it('enable submit when a branch is selected', () => {
|
||||
clickElementAtIndex(1);
|
||||
|
||||
expect(submitBtn().getAttribute('disabled')).toBe(null);
|
||||
});
|
||||
|
||||
it('triggers change.branch event on a branch click', () => {
|
||||
spyOnEvent(dropdown.$dropdown, 'change.branch');
|
||||
clickElementAtIndex(0);
|
||||
|
||||
expect('change.branch').toHaveBeenTriggeredOn(dropdown.$dropdown);
|
||||
});
|
||||
|
||||
describe('#dropdownData', () => {
|
||||
it('cache the refs', () => {
|
||||
const refs = dropdown.cachedRefs;
|
||||
dropdown.cachedRefs = null;
|
||||
|
||||
dropdown.dropdownData(refs);
|
||||
|
||||
expect(dropdown.cachedRefs).toEqual(refs);
|
||||
});
|
||||
|
||||
it('returns the Branches with the newBranch and defaultBranch', () => {
|
||||
const refs = dropdown.cachedRefs;
|
||||
dropdown.branchInput.value = 'master';
|
||||
dropdown.newBranch = { id: 'new_branch', text: 'new_branch', title: 'new_branch' };
|
||||
|
||||
const branches = dropdown.dropdownData(refs).Branches;
|
||||
|
||||
expect(branches.length).toEqual(4);
|
||||
expect(branches[0]).toEqual(dropdown.newBranch);
|
||||
expect(branches[1]).toEqual({ id: 'master', text: 'master', title: 'master' });
|
||||
expect(branches[2]).toEqual({ id: 'development', text: 'development', title: 'development' });
|
||||
expect(branches[3]).toEqual({ id: 'staging', text: 'staging', title: 'staging' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setNewBranch', () => {
|
||||
it('adds the new branch and select it', () => {
|
||||
const branchName = 'new_branch';
|
||||
|
||||
dropdown.setNewBranch(branchName);
|
||||
|
||||
expect(elementAtIndex(0)).toHaveClass('is-active');
|
||||
expect(elementAtIndex(0)).toContainHtml(branchName);
|
||||
});
|
||||
|
||||
it("doesn't add a new branch if already exists in the list", () => {
|
||||
const branchName = elementAtIndex(0).text;
|
||||
const initialLength = element().length;
|
||||
|
||||
dropdown.setNewBranch(branchName);
|
||||
|
||||
expect(element().length).toEqual(initialLength);
|
||||
});
|
||||
|
||||
it('clears the search filter', () => {
|
||||
const branchName = elementAtIndex(0).text;
|
||||
searchField().value = 'searching';
|
||||
|
||||
dropdown.setNewBranch(branchName);
|
||||
|
||||
expect(searchField().value).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
5
spec/javascripts/fixtures/project_branches.json
Normal file
5
spec/javascripts/fixtures/project_branches.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
[
|
||||
"master",
|
||||
"development",
|
||||
"staging"
|
||||
]
|
28
spec/javascripts/fixtures/target_branch_dropdown.html.haml
Normal file
28
spec/javascripts/fixtures/target_branch_dropdown.html.haml
Normal file
|
@ -0,0 +1,28 @@
|
|||
%form.js-edit-blob-form
|
||||
%input{type: 'hidden', name: 'target_branch', value: 'master'}
|
||||
%div
|
||||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-project-branches-dropdown.js-target-branch{type: 'button', data: {toggle: 'dropdown', selected: 'master', field_name: 'target_branch', form_id: '.js-edit-blob-form'}}
|
||||
.dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging
|
||||
.dropdown-page-one
|
||||
.dropdown-title 'Select branch'
|
||||
.dropdown-input
|
||||
%input.dropdown-input-field{type: 'search', value: ''}
|
||||
%i.fa.fa-search.dropdown-input-search
|
||||
%i.fa.fa-times-dropdown-input-clear.js-dropdown-input-clear{role: 'button'}
|
||||
.dropdown-content
|
||||
.dropdown-footer
|
||||
%ul.dropdown-footer-list
|
||||
%li
|
||||
%a.create-new-branch.dropdown-toggle-page{href: "#"}
|
||||
Create new branch
|
||||
.dropdown-page-two.dropdown-new-branch
|
||||
%button.dropdown-title-button.dropdown-menu-back{type: 'button'}
|
||||
.dropdown_title 'Create new branch'
|
||||
.dropdown_content
|
||||
%input#new_branch_name.default-dropdown-input{ type: "text", placeholder: "Name new branch" }
|
||||
%button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" }
|
||||
Create
|
||||
%button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" }
|
||||
Cancel
|
||||
%button{type: 'submit'}
|
Loading…
Reference in a new issue