Merge branch 'ph-multi-file-editor-new-file-folder-dropdown' into 'master'

Add new files & directories in the multi-file editor

Closes #38614

See merge request gitlab-org/gitlab-ce!14839
This commit is contained in:
Filipa Lacerda 2017-10-24 09:06:55 +00:00
commit cc17067085
25 changed files with 708 additions and 52 deletions

View File

@ -0,0 +1,86 @@
<script>
import RepoStore from '../../stores/repo_store';
import RepoHelper from '../../helpers/repo_helper';
import eventHub from '../../event_hub';
import newModal from './modal.vue';
export default {
components: {
newModal,
},
data() {
return {
openModal: false,
modalType: '',
currentPath: RepoStore.path,
};
},
methods: {
createNewItem(type) {
this.modalType = type;
this.toggleModalOpen();
},
toggleModalOpen() {
this.openModal = !this.openModal;
},
createNewEntryInStore(name, type) {
RepoHelper.createNewEntry(name, type);
this.toggleModalOpen();
},
},
created() {
eventHub.$on('createNewEntry', this.createNewEntryInStore);
},
beforeDestroy() {
eventHub.$off('createNewEntry', this.createNewEntryInStore);
},
};
</script>
<template>
<div>
<ul class="breadcrumb repo-breadcrumb">
<li class="dropdown">
<button
type="button"
class="btn btn-default dropdown-toggle add-to-tree"
data-toggle="dropdown"
aria-label="Create new file or directory"
>
<i
class="fa fa-plus"
aria-hidden="true"
>
</i>
</button>
<ul class="dropdown-menu">
<li>
<a
href="#"
role="button"
@click.prevent="createNewItem('blob')"
>
{{ __('New file') }}
</a>
</li>
<li>
<a
href="#"
role="button"
@click.prevent="createNewItem('tree')"
>
{{ __('New directory') }}
</a>
</li>
</ul>
</li>
</ul>
<new-modal
v-if="openModal"
:type="modalType"
:current-path="currentPath"
@toggle="toggleModalOpen"
/>
</div>
</template>

View File

@ -0,0 +1,90 @@
<script>
import { __ } from '../../../locale';
import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
import eventHub from '../../event_hub';
export default {
props: {
currentPath: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
},
data() {
return {
entryName: this.currentPath !== '' ? `${this.currentPath}/` : '',
};
},
components: {
popupDialog,
},
methods: {
createEntryInStore() {
eventHub.$emit('createNewEntry', this.entryName, this.type);
},
toggleModalOpen() {
this.$emit('toggle');
},
},
computed: {
modalTitle() {
if (this.type === 'tree') {
return __('Create new directory');
}
return __('Create new file');
},
buttonLabel() {
if (this.type === 'tree') {
return __('Create directory');
}
return __('Create file');
},
formLabelName() {
if (this.type === 'tree') {
return __('Directory name');
}
return __('File name');
},
},
mounted() {
this.$refs.fieldName.focus();
},
};
</script>
<template>
<popup-dialog
:title="modalTitle"
:primary-button-label="buttonLabel"
kind="success"
@toggle="toggleModalOpen"
@submit="createEntryInStore"
>
<form
class="form-horizontal"
slot="body"
@submit.prevent="createEntryInStore"
>
<fieldset class="form-group append-bottom-0">
<label class="label-light col-sm-3">
{{ formLabelName }}
</label>
<div class="col-sm-9">
<input
type="text"
class="form-control"
v-model="entryName"
ref="fieldName"
/>
</div>
</fieldset>
</form>
</popup-dialog>
</template>

View File

@ -46,6 +46,10 @@ export default {
dialogSubmitted(status) {
this.toggleDialogOpen(false);
this.dialog.status = status;
// remove tmp files
Helper.removeAllTmpFiles('openedFiles');
Helper.removeAllTmpFiles('files');
},
toggleBlobView: Store.toggleBlobView,
createNewBranch(branch) {

View File

@ -49,7 +49,7 @@ export default {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const commitMessage = this.commitMessage;
const actions = this.changedFiles.map(f => ({
action: 'update',
action: f.tempFile ? 'create' : 'update',
file_path: f.path,
content: f.newContent,
}));
@ -62,7 +62,6 @@ export default {
if (newBranch) {
payload.start_branch = this.currentBranch;
}
this.submitCommitsLoading = true;
Service.commitFiles(payload)
.then(() => {
this.resetCommitState();
@ -78,6 +77,8 @@ export default {
},
tryCommit(e, skipBranchCheck = false, newBranch = false) {
this.submitCommitsLoading = true;
if (skipBranchCheck) {
this.makeCommit(newBranch);
} else {
@ -90,6 +91,7 @@ export default {
this.makeCommit(newBranch);
})
.catch(() => {
this.submitCommitsLoading = false;
Flash('An error occurred while committing your changes');
});
}

View File

@ -16,7 +16,7 @@ const RepoEditor = {
},
mounted() {
Service.getRaw(this.activeFile.raw_path)
Service.getRaw(this.activeFile)
.then((rawResponse) => {
Store.blobRaw = rawResponse.data;
Store.activeFile.plain = rawResponse.data;

View File

@ -11,7 +11,12 @@ const RepoFileButtons = {
mixins: [RepoMixin],
computed: {
showButtons() {
return this.activeFile.raw_path ||
this.activeFile.blame_path ||
this.activeFile.commits_path ||
this.activeFile.permalink;
},
rawDownloadButtonLabel() {
return this.binary ? 'Download' : 'Raw';
},
@ -30,7 +35,10 @@ export default RepoFileButtons;
</script>
<template>
<div id="repo-file-buttons">
<div
v-if="showButtons"
class="repo-file-buttons"
>
<a
:href="activeFile.raw_path"
target="_blank"

View File

@ -18,8 +18,8 @@ const RepoTab = {
},
changedClass() {
const tabChangedObj = {
'fa-times close-icon': !this.tab.changed,
'fa-circle unsaved-icon': this.tab.changed,
'fa-times close-icon': !this.tab.changed && !this.tab.tempFile,
'fa-circle unsaved-icon': this.tab.changed || this.tab.tempFile,
};
return tabChangedObj;
},
@ -30,7 +30,7 @@ const RepoTab = {
Store.setActiveFiles(file);
},
closeTab(file) {
if (file.changed) return;
if (file.changed || file.tempFile) return;
Store.removeFromOpenedFiles(file);
},

View File

@ -1,4 +1,3 @@
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
import Service from '../services/repo_service';
import Store from '../stores/repo_store';
import Flash from '../../flash';
@ -8,6 +7,7 @@ const RepoHelper = {
getDefaultActiveFile() {
return {
id: '',
active: true,
binary: false,
extension: '',
@ -62,6 +62,7 @@ const RepoHelper = {
});
RepoHelper.updateHistoryEntry(tree.url, title);
Store.path = tree.path;
},
setDirectoryToClosed(entry) {
@ -96,8 +97,8 @@ const RepoHelper = {
.then((response) => {
const data = response.data;
if (response.headers && response.headers['page-title']) data.pageTitle = decodeURI(response.headers['page-title']);
if (response.headers && response.headers['is-root'] && !Store.isInitialRoot) {
Store.isRoot = convertPermissionToBoolean(response.headers['is-root']);
if (data.path && !Store.isInitialRoot) {
Store.isRoot = data.path === '/';
Store.isInitialRoot = Store.isRoot;
}
@ -110,7 +111,7 @@ const RepoHelper = {
RepoHelper.setBinaryDataAsBase64(data);
Store.setViewToPreview();
} else if (!Store.isPreviewView() && !data.render_error) {
Service.getRaw(data.raw_path)
Service.getRaw(data)
.then((rawResponse) => {
Store.blobRaw = rawResponse.data;
data.plain = rawResponse.data;
@ -138,6 +139,10 @@ const RepoHelper = {
addToDirectory(file, data) {
const tree = file || Store;
// TODO: Figure out why `popstate` is being trigger in the specs
if (!tree.files) return;
const files = tree.files.concat(this.dataToListOfFiles(data, file ? file.level + 1 : 0));
tree.files = files;
@ -157,7 +162,18 @@ const RepoHelper = {
},
serializeRepoEntity(type, entity, level = 0) {
const { id, url, name, icon, last_commit, tree_url } = entity;
const {
id,
url,
name,
icon,
last_commit,
tree_url,
path,
tempFile,
active,
opened,
} = entity;
return {
id,
@ -165,11 +181,14 @@ const RepoHelper = {
name,
url,
tree_url,
path,
level,
tempFile,
icon: `fa-${icon}`,
files: [],
loading: false,
opened: false,
opened,
active,
// eslint-disable-next-line camelcase
lastCommit: last_commit ? {
url: `${Store.projectUrl}/commit/${last_commit.id}`,
@ -213,7 +232,7 @@ const RepoHelper = {
},
findOpenedFileFromActive() {
return Store.openedFiles.find(openedFile => Store.activeFile.url === openedFile.url);
return Store.openedFiles.find(openedFile => Store.activeFile.id === openedFile.id);
},
getFileFromPath(path) {
@ -223,6 +242,76 @@ const RepoHelper = {
loadingError() {
Flash('Unable to load this content at this time.');
},
openEditMode() {
Store.editMode = true;
Store.currentBlobView = 'repo-editor';
},
updateStorePath(path) {
Store.path = path;
},
findOrCreateEntry(type, tree, name) {
let exists = true;
let foundEntry = tree.files.find(dir => dir.type === type && dir.name === name);
if (!foundEntry) {
foundEntry = RepoHelper.serializeRepoEntity(type, {
id: name,
name,
path: tree.path ? `${tree.path}/${name}` : name,
icon: type === 'tree' ? 'folder' : 'file-text-o',
tempFile: true,
opened: true,
active: true,
}, tree.level !== undefined ? tree.level + 1 : 0);
exists = false;
tree.files.push(foundEntry);
}
return {
entry: foundEntry,
exists,
};
},
removeAllTmpFiles(storeFilesKey) {
Store[storeFilesKey] = Store[storeFilesKey].filter(f => !f.tempFile);
},
createNewEntry(name, type) {
const originalPath = Store.path;
let entryName = name;
if (entryName.indexOf(`${originalPath}/`) !== 0) {
this.updateStorePath('');
} else {
entryName = entryName.replace(`${originalPath}/`, '');
}
if (entryName === '') return;
const fileName = type === 'tree' ? '.gitkeep' : entryName;
let tree = Store;
if (type === 'tree') {
const dirNames = entryName.split('/');
dirNames.forEach((dirName) => {
if (dirName === '') return;
tree = this.findOrCreateEntry('tree', tree, dirName).entry;
});
}
if ((type === 'tree' && tree.tempFile) || type === 'blob') {
const file = this.findOrCreateEntry('blob', tree, fileName);
if (!file.exists) {
this.setFile(file.entry, file.entry);
this.openEditMode();
}
}
this.updateStorePath(originalPath);
},
};
export default RepoHelper;

View File

@ -6,6 +6,7 @@ import Store from './stores/repo_store';
import Repo from './components/repo.vue';
import RepoEditButton from './components/repo_edit_button.vue';
import newBranchForm from './components/new_branch_form.vue';
import newDropdown from './components/new_dropdown/index.vue';
import Translate from '../vue_shared/translate';
function initDropdowns() {
@ -28,6 +29,7 @@ function setInitialStore(data) {
Store.service = Service;
Store.service.url = data.url;
Store.service.refsUrl = data.refsUrl;
Store.path = data.currentPath;
Store.projectId = data.projectId;
Store.projectName = data.projectName;
Store.projectUrl = data.projectUrl;
@ -63,6 +65,18 @@ function initRepoEditButton(el) {
});
}
function initNewDropdown(el) {
return new Vue({
el,
components: {
newDropdown,
},
render(createElement) {
return createElement('new-dropdown');
},
});
}
function initNewBranchForm() {
const el = document.querySelector('.js-new-branch-dropdown');
@ -86,6 +100,7 @@ function initNewBranchForm() {
function initRepoBundle() {
const repo = document.getElementById('repo');
const editButton = document.querySelector('.editable-mode');
const newDropdownHolder = document.querySelector('.js-new-dropdown');
setInitialStore(repo.dataset);
addEventsForNonVueEls();
initDropdowns();
@ -95,6 +110,7 @@ function initRepoBundle() {
initRepo(repo);
initRepoEditButton(editButton);
initNewBranchForm();
initNewDropdown(newDropdownHolder);
}
$(initRepoBundle);

View File

@ -8,7 +8,7 @@ const RepoMixin = {
changedFiles() {
const changedFileList = this.openedFiles
.filter(file => file.changed);
.filter(file => file.changed || file.tempFile);
return changedFileList;
},
},

View File

@ -16,8 +16,14 @@ const RepoService = {
createBranchPath: '/api/:version/projects/:id/repository/branches',
richExtensionRegExp: /md/,
getRaw(url) {
return axios.get(url, {
getRaw(file) {
if (file.tempFile) {
return Promise.resolve({
data: '',
});
}
return axios.get(file.raw_path, {
// Stop Axios from parsing a JSON file into a JS object
transformResponse: [res => res],
});

View File

@ -39,6 +39,7 @@ const RepoStore = {
newMrTemplateUrl: '',
branchChanged: false,
commitMessage: '',
path: '',
loading: {
tree: false,
blob: false,
@ -77,21 +78,23 @@ const RepoStore = {
} else if (file.newContent || file.plain) {
RepoStore.blobRaw = file.newContent || file.plain;
} else {
Service.getRaw(file.raw_path)
Service.getRaw(file)
.then((rawResponse) => {
RepoStore.blobRaw = rawResponse.data;
Helper.findOpenedFileFromActive().plain = rawResponse.data;
}).catch(Helper.loadingError);
}
if (!file.loading) Helper.updateHistoryEntry(file.url, file.pageTitle || file.name);
if (!file.loading && !file.tempFile) {
Helper.updateHistoryEntry(file.url, file.pageTitle || file.name);
}
RepoStore.binary = file.binary;
RepoStore.setActiveLine(-1);
},
setFileActivity(file, openedFile, i) {
const activeFile = openedFile;
activeFile.active = file.url === activeFile.url;
activeFile.active = file.id === activeFile.id;
if (activeFile.active) RepoStore.setActiveFile(activeFile, i);
@ -99,7 +102,7 @@ const RepoStore = {
},
setActiveFile(activeFile, i) {
RepoStore.activeFile = Object.assign({}, RepoStore.activeFile, activeFile);
RepoStore.activeFile = Object.assign({}, Helper.getDefaultActiveFile(), activeFile);
RepoStore.activeFileIndex = i;
},
@ -121,6 +124,11 @@ const RepoStore = {
return openedFile.path !== file.path;
});
// remove the file from the sidebar if it is a tempFile
if (file.tempFile) {
RepoStore.files = RepoStore.files.filter(f => !(f.tempFile && f.path === file.path));
}
// now activate the right tab based on what you closed.
if (RepoStore.openedFiles.length === 0) {
RepoStore.activeFile = {};
@ -170,7 +178,7 @@ const RepoStore = {
// getters
isActiveFile(file) {
return file && file.url === RepoStore.activeFile.url;
return file && file.id === RepoStore.activeFile.id;
},
isPreviewView() {

View File

@ -9,7 +9,7 @@ export default {
},
text: {
type: String,
required: true,
required: false,
},
kind: {
type: String,
@ -82,14 +82,15 @@ export default {
type="button"
class="btn"
:class="btnCancelKindClass"
@click="emitSubmit(false)">
{{closeButtonLabel}}
@click="close">
{{ closeButtonLabel }}
</button>
<button type="button"
<button
type="button"
class="btn"
:class="btnKindClass"
@click="emitSubmit(true)">
{{primaryButtonLabel}}
{{ primaryButtonLabel }}
</button>
</div>
</div>

View File

@ -201,7 +201,7 @@
}
}
#repo-file-buttons {
.repo-file-buttons {
background-color: $white-light;
padding: 5px 10px;
border-top: 1px solid $white-normal;

View File

@ -205,6 +205,7 @@ class Projects::BlobController < Projects::ApplicationController
tree_path = path_segments.join('/')
render json: json.merge(
id: @blob.id,
path: blob.path,
name: blob.name,
extension: blob.extension,

View File

@ -36,7 +36,6 @@ class Projects::TreeController < Projects::ApplicationController
format.json do
page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
response.header['is-root'] = @path.empty?
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
Gitlab::GitalyClient.allow_n_plus_1_calls do

View File

@ -2,7 +2,9 @@
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
- unless show_new_repo?
- if show_new_repo?
.js-new-dropdown
- else
= render 'projects/tree/old_tree_header'
.tree-controls

View File

@ -7,4 +7,5 @@
blob_url: namespace_project_blob_path(project.namespace, project, '{{branch}}'),
new_mr_template_url: namespace_project_new_merge_request_path(project.namespace, project, merge_request: { source_branch: '{{source_branch}}' }),
can_commit: (!!can_push_branch?(project, @ref)).to_s,
on_top_of_branch: (!!on_top_of_branch?(project, @ref)).to_s } }
on_top_of_branch: (!!on_top_of_branch?(project, @ref)).to_s,
current_path: @path } }

View File

@ -0,0 +1,43 @@
require 'spec_helper'
feature 'Multi-file editor new directory', :js do
include WaitForRequests
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
before do
project.add_master(user)
sign_in(user)
page.driver.set_cookie('new_repo', 'true')
visit project_tree_path(project, :master)
wait_for_requests
end
it 'creates directory in current directory' do
find('.add-to-tree').click
click_link('New directory')
page.within('.popup-dialog') do
find('.form-control').set('foldername')
click_button('Create directory')
end
fill_in('commit-message', with: 'commit message')
click_button('Commit 1 file')
expect(page).to have_content('Your changes have been committed')
expect(page).to have_selector('td', text: 'commit message')
click_link('foldername')
expect(page).to have_selector('td', text: 'commit message', count: 2)
expect(page).to have_selector('td', text: '.gitkeep')
end
end

View File

@ -0,0 +1,40 @@
require 'spec_helper'
feature 'Multi-file editor new file', :js do
include WaitForRequests
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
before do
project.add_master(user)
sign_in(user)
page.driver.set_cookie('new_repo', 'true')
visit project_tree_path(project, :master)
wait_for_requests
end
it 'creates file in current directory' do
find('.add-to-tree').click
click_link('New file')
page.within('.popup-dialog') do
find('.form-control').set('filename')
click_button('Create file')
end
find('.inputarea').send_keys('file content')
fill_in('commit-message', with: 'commit message')
click_button('Commit 1 file')
expect(page).to have_content('Your changes have been committed')
expect(page).to have_selector('td', text: 'commit message')
end
end

View File

@ -1,4 +1,3 @@
export default (Component, props = {}) => new Component({
export default (Component, props = {}, el = null) => new Component({
propsData: props,
}).$mount();
}).$mount(el);

View File

@ -0,0 +1,191 @@
import Vue from 'vue';
import newDropdown from '~/repo/components/new_dropdown/index.vue';
import RepoStore from '~/repo/stores/repo_store';
import RepoHelper from '~/repo/helpers/repo_helper';
import eventHub from '~/repo/event_hub';
import createComponent from '../../../helpers/vue_mount_component_helper';
describe('new dropdown component', () => {
let vm;
beforeEach(() => {
const component = Vue.extend(newDropdown);
vm = createComponent(component);
});
afterEach(() => {
vm.$destroy();
RepoStore.files = [];
RepoStore.openedFiles = [];
RepoStore.setViewToPreview();
});
it('renders new file and new directory links', () => {
expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file');
expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('New directory');
});
describe('createNewItem', () => {
it('sets modalType to blob when new file is clicked', () => {
vm.$el.querySelectorAll('a')[0].click();
expect(vm.modalType).toBe('blob');
});
it('sets modalType to tree when new directory is clicked', () => {
vm.$el.querySelectorAll('a')[1].click();
expect(vm.modalType).toBe('tree');
});
it('opens modal when link is clicked', (done) => {
vm.$el.querySelectorAll('a')[0].click();
Vue.nextTick(() => {
expect(vm.$el.querySelector('.modal')).not.toBeNull();
done();
});
});
});
describe('toggleModalOpen', () => {
it('closes modal after toggling', (done) => {
vm.toggleModalOpen();
Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('.modal')).not.toBeNull();
})
.then(vm.toggleModalOpen)
.then(() => {
expect(vm.$el.querySelector('.modal')).toBeNull();
})
.then(done)
.catch(done.fail);
});
});
describe('createEntryInStore', () => {
['tree', 'blob'].forEach((type) => {
describe(type, () => {
it('closes modal after creating file', () => {
vm.openModal = true;
eventHub.$emit('createNewEntry', 'testing', type);
expect(vm.openModal).toBeFalsy();
});
it('sets editMode to true', () => {
eventHub.$emit('createNewEntry', 'testing', type);
expect(RepoStore.editMode).toBeTruthy();
});
it('toggles blob view', () => {
eventHub.$emit('createNewEntry', 'testing', type);
expect(RepoStore.isPreviewView()).toBeFalsy();
});
it('adds file into activeFiles', () => {
eventHub.$emit('createNewEntry', 'testing', type);
expect(RepoStore.openedFiles.length).toBe(1);
});
it(`creates ${type} in the current stores path`, () => {
RepoStore.path = 'testing';
eventHub.$emit('createNewEntry', 'testing/app', type);
expect(RepoStore.files[0].path).toBe('testing/app');
expect(RepoStore.files[0].name).toBe('app');
if (type === 'tree') {
expect(RepoStore.files[0].files.length).toBe(1);
}
RepoStore.path = '';
});
});
});
describe('file', () => {
it('creates new file', () => {
eventHub.$emit('createNewEntry', 'testing', 'blob');
expect(RepoStore.files.length).toBe(1);
expect(RepoStore.files[0].name).toBe('testing');
expect(RepoStore.files[0].type).toBe('blob');
expect(RepoStore.files[0].tempFile).toBeTruthy();
});
it('does not create temp file when file already exists', () => {
RepoStore.files.push(RepoHelper.serializeRepoEntity('blob', {
name: 'testing',
}));
eventHub.$emit('createNewEntry', 'testing', 'blob');
expect(RepoStore.files.length).toBe(1);
expect(RepoStore.files[0].name).toBe('testing');
expect(RepoStore.files[0].type).toBe('blob');
expect(RepoStore.files[0].tempFile).toBeUndefined();
});
});
describe('tree', () => {
it('creates new tree', () => {
eventHub.$emit('createNewEntry', 'testing', 'tree');
expect(RepoStore.files.length).toBe(1);
expect(RepoStore.files[0].name).toBe('testing');
expect(RepoStore.files[0].type).toBe('tree');
expect(RepoStore.files[0].tempFile).toBeTruthy();
expect(RepoStore.files[0].files.length).toBe(1);
expect(RepoStore.files[0].files[0].name).toBe('.gitkeep');
});
it('creates multiple trees when entryName has slashes', () => {
eventHub.$emit('createNewEntry', 'app/test', 'tree');
expect(RepoStore.files.length).toBe(1);
expect(RepoStore.files[0].name).toBe('app');
expect(RepoStore.files[0].files[0].name).toBe('test');
expect(RepoStore.files[0].files[0].files[0].name).toBe('.gitkeep');
});
it('creates tree in existing tree', () => {
RepoStore.files.push(RepoHelper.serializeRepoEntity('tree', {
name: 'app',
}));
eventHub.$emit('createNewEntry', 'app/test', 'tree');
expect(RepoStore.files.length).toBe(1);
expect(RepoStore.files[0].name).toBe('app');
expect(RepoStore.files[0].tempFile).toBeUndefined();
expect(RepoStore.files[0].files[0].tempFile).toBeTruthy();
expect(RepoStore.files[0].files[0].name).toBe('test');
expect(RepoStore.files[0].files[0].files[0].name).toBe('.gitkeep');
});
it('does not create new tree when already exists', () => {
RepoStore.files.push(RepoHelper.serializeRepoEntity('tree', {
name: 'app',
}));
eventHub.$emit('createNewEntry', 'app', 'tree');
expect(RepoStore.files.length).toBe(1);
expect(RepoStore.files[0].name).toBe('app');
expect(RepoStore.files[0].tempFile).toBeUndefined();
expect(RepoStore.files[0].files.length).toBe(0);
});
});
});
});

View File

@ -0,0 +1,76 @@
import Vue from 'vue';
import RepoStore from '~/repo/stores/repo_store';
import modal from '~/repo/components/new_dropdown/modal.vue';
import eventHub from '~/repo/event_hub';
import createComponent from '../../../helpers/vue_mount_component_helper';
describe('new file modal component', () => {
const Component = Vue.extend(modal);
let vm;
afterEach(() => {
vm.$destroy();
RepoStore.files = [];
RepoStore.openedFiles = [];
RepoStore.setViewToPreview();
});
['tree', 'blob'].forEach((type) => {
describe(type, () => {
beforeEach(() => {
vm = createComponent(Component, {
type,
currentPath: RepoStore.path,
});
});
it(`sets modal title as ${type}`, () => {
const title = type === 'tree' ? 'directory' : 'file';
expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`);
});
it(`sets button label as ${type}`, () => {
const title = type === 'tree' ? 'directory' : 'file';
expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`);
});
it(`sets form label as ${type}`, () => {
const title = type === 'tree' ? 'Directory' : 'File';
expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(`${title} name`);
});
});
});
it('focuses field on mount', () => {
document.body.innerHTML += '<div class="js-test"></div>';
vm = createComponent(Component, {
type: 'tree',
currentPath: RepoStore.path,
}, '.js-test');
expect(document.activeElement).toBe(vm.$refs.fieldName);
vm.$el.remove();
});
describe('createEntryInStore', () => {
it('emits createNewEntry event', () => {
spyOn(eventHub, '$emit');
vm = createComponent(Component, {
type: 'tree',
currentPath: RepoStore.path,
});
vm.entryName = 'testing';
vm.createEntryInStore();
expect(eventHub.$emit).toHaveBeenCalledWith('createNewEntry', 'testing', 'tree');
});
});
});

View File

@ -3,6 +3,15 @@ import repoFileButtons from '~/repo/components/repo_file_buttons.vue';
import RepoStore from '~/repo/stores/repo_store';
describe('RepoFileButtons', () => {
const activeFile = {
extension: 'md',
url: 'url',
raw_path: 'raw_path',
blame_path: 'blame_path',
commits_path: 'commits_path',
permalink: 'permalink',
};
function createComponent() {
const RepoFileButtons = Vue.extend(repoFileButtons);
@ -14,14 +23,6 @@ describe('RepoFileButtons', () => {
});
it('renders Raw, Blame, History, Permalink and Preview toggle', () => {
const activeFile = {
extension: 'md',
url: 'url',
raw_path: 'raw_path',
blame_path: 'blame_path',
commits_path: 'commits_path',
permalink: 'permalink',
};
const activeFileLabel = 'activeFileLabel';
RepoStore.openedFiles = new Array(1);
RepoStore.activeFile = activeFile;
@ -34,7 +35,6 @@ describe('RepoFileButtons', () => {
const blame = vm.$el.querySelector('.blame');
const history = vm.$el.querySelector('.history');
expect(vm.$el.id).toEqual('repo-file-buttons');
expect(raw.href).toMatch(`/${activeFile.raw_path}`);
expect(raw.textContent.trim()).toEqual('Raw');
expect(blame.href).toMatch(`/${activeFile.blame_path}`);
@ -46,10 +46,6 @@ describe('RepoFileButtons', () => {
});
it('triggers rawPreviewToggle on preview click', () => {
const activeFile = {
extension: 'md',
url: 'url',
};
RepoStore.openedFiles = new Array(1);
RepoStore.activeFile = activeFile;
RepoStore.editMode = true;
@ -65,10 +61,7 @@ describe('RepoFileButtons', () => {
});
it('does not render preview toggle if not canPreview', () => {
const activeFile = {
extension: 'abcd',
url: 'url',
};
activeFile.extension = 'js';
RepoStore.openedFiles = new Array(1);
RepoStore.activeFile = activeFile;

View File

@ -7,6 +7,7 @@ import { file } from '../mock_data';
describe('RepoFile', () => {
const updated = 'updated';
const otherFile = {
id: 'test',
html: '<p class="file-content">html</p>',
pageTitle: 'otherpageTitle',
};