Commit be74e393 authored by Phil Hughes's avatar Phil Hughes

Improvements to new entry dropdowns in Web IDE

Closes #44845
parent e68a547b
<script> <script>
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import NewModal from './new_dropdown/modal.vue';
import IdeSidebar from './ide_side_bar.vue'; import IdeSidebar from './ide_side_bar.vue';
import RepoTabs from './repo_tabs.vue'; import RepoTabs from './repo_tabs.vue';
import IdeStatusBar from './ide_status_bar.vue'; import IdeStatusBar from './ide_status_bar.vue';
...@@ -13,6 +14,7 @@ const originalStopCallback = Mousetrap.stopCallback; ...@@ -13,6 +14,7 @@ const originalStopCallback = Mousetrap.stopCallback;
export default { export default {
components: { components: {
NewModal,
IdeSidebar, IdeSidebar,
RepoTabs, RepoTabs,
IdeStatusBar, IdeStatusBar,
...@@ -137,5 +139,6 @@ export default { ...@@ -137,5 +139,6 @@ export default {
/> />
</div> </div>
<ide-status-bar :file="activeFile"/> <ide-status-bar :file="activeFile"/>
<new-modal />
</article> </article>
</template> </template>
<script> <script>
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import NewDropdown from './new_dropdown/index.vue'; import Icon from '~/vue_shared/components/icon.vue';
import IdeTreeList from './ide_tree_list.vue'; import IdeTreeList from './ide_tree_list.vue';
import Upload from './new_dropdown/upload.vue';
import NewEntryButton from './new_dropdown/button.vue';
export default { export default {
components: { components: {
NewDropdown, Icon,
Upload,
IdeTreeList, IdeTreeList,
NewEntryButton,
}, },
computed: { computed: {
...mapState(['currentBranchId']), ...mapState(['currentBranchId']),
...@@ -20,23 +24,42 @@ export default { ...@@ -20,23 +24,42 @@ export default {
} }
}, },
methods: { methods: {
...mapActions(['updateViewer']), ...mapActions(['updateViewer', 'openNewEntryModal', 'createTempEntry']),
}, },
}; };
</script> </script>
<template> <template>
<ide-tree-list <ide-tree-list
header-class="d-flex w-100"
viewer-type="editor" viewer-type="editor"
> >
<template <template
slot="header" slot="header"
> >
{{ __('Edit') }} {{ __('Edit') }}
<new-dropdown <div class="ml-auto d-flex">
:project-id="currentProject.name_with_namespace" <new-entry-button
:branch="currentBranchId" :label="__('New file')"
/> :show-label="false"
class="d-flex border-0 p-0 mr-3"
icon="doc-new"
@click="openNewEntryModal({ type: 'blob' })"
/>
<upload
:show-label="false"
class="d-flex mr-3"
button-css-classes="border-0 p-0"
@create="createTempEntry"
/>
<new-entry-button
:label="__('New directory')"
:show-label="false"
class="d-flex border-0 p-0"
icon="folder-new"
@click="openNewEntryModal({ type: 'tree' })"
/>
</div>
</template> </template>
</ide-tree-list> </ide-tree-list>
</template> </template>
<script>
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
Icon,
},
props: {
label: {
type: String,
required: false,
default: null,
},
icon: {
type: String,
required: true,
},
iconClasses: {
type: String,
required: false,
default: null,
},
showLabel: {
type: Boolean,
required: false,
default: true,
},
},
methods: {
clicked() {
this.$emit('click');
},
},
};
</script>
<template>
<button
:aria-label="label"
type="button"
@click.stop.prevent="clicked"
>
<icon
:name="icon"
:css-classes="iconClasses"
/>
<template v-if="showLabel">
{{ label }}
</template>
</button>
</template>
...@@ -3,12 +3,14 @@ import { mapActions } from 'vuex'; ...@@ -3,12 +3,14 @@ import { mapActions } from 'vuex';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import newModal from './modal.vue'; import newModal from './modal.vue';
import upload from './upload.vue'; import upload from './upload.vue';
import ItemButton from './button.vue';
export default { export default {
components: { components: {
icon, icon,
newModal, newModal,
upload, upload,
ItemButton,
}, },
props: { props: {
branch: { branch: {
...@@ -20,11 +22,13 @@ export default { ...@@ -20,11 +22,13 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
mouseOver: {
type: Boolean,
required: true,
},
}, },
data() { data() {
return { return {
openModal: false,
modalType: '',
dropdownOpen: false, dropdownOpen: false,
}; };
}, },
...@@ -34,17 +38,18 @@ export default { ...@@ -34,17 +38,18 @@ export default {
this.$refs.dropdownMenu.scrollIntoView(); this.$refs.dropdownMenu.scrollIntoView();
}); });
}, },
mouseOver() {
if (!this.mouseOver) {
this.dropdownOpen = false;
}
},
}, },
methods: { methods: {
...mapActions(['createTempEntry']), ...mapActions(['createTempEntry', 'openNewEntryModal']),
createNewItem(type) { createNewItem(type) {
this.modalType = type; this.openNewEntryModal({ type, path: this.path });
this.openModal = true;
this.dropdownOpen = false; this.dropdownOpen = false;
}, },
hideModal() {
this.openModal = false;
},
openDropdown() { openDropdown() {
this.dropdownOpen = !this.dropdownOpen; this.dropdownOpen = !this.dropdownOpen;
}, },
...@@ -58,23 +63,19 @@ export default { ...@@ -58,23 +63,19 @@ export default {
:class="{ :class="{
show: dropdownOpen, show: dropdownOpen,
}" }"
class="dropdown" class="dropdown d-flex"
> >
<button <button
:aria-label="__('Create new file or directory')"
type="button" type="button"
class="btn btn-sm btn-default dropdown-toggle add-to-tree" class="rounded border-0 d-flex ide-entry-dropdown-toggle"
aria-label="Create new file or directory"
@click.stop="openDropdown()" @click.stop="openDropdown()"
> >
<icon <icon
:size="12" name="hamburger"
name="plus"
css-classes="float-left"
/> />
<icon <icon
:size="12"
name="arrow-down" name="arrow-down"
css-classes="float-left"
/> />
</button> </button>
<ul <ul
...@@ -82,39 +83,30 @@ export default { ...@@ -82,39 +83,30 @@ export default {
class="dropdown-menu dropdown-menu-right" class="dropdown-menu dropdown-menu-right"
> >
<li> <li>
<a <item-button
href="#" :label="__('New file')"
role="button" class="d-flex"
@click.stop.prevent="createNewItem('blob')" icon="doc-new"
> icon-classes="mr-2"
{{ __('New file') }} @click="createNewItem('blob')"
</a> />
</li> </li>
<li> <li>
<upload <upload
:branch-id="branch"
:path="path" :path="path"
@create="createTempEntry" @create="createTempEntry"
/> />
</li> </li>
<li> <li>
<a <item-button
href="#" :label="__('New directory')"
role="button" class="d-flex"
@click.stop.prevent="createNewItem('tree')" icon="folder-new"
> icon-classes="mr-2"
{{ __('New directory') }} @click="createNewItem('tree')"
</a> />
</li> </li>
</ul> </ul>
</div> </div>
<new-modal
v-if="openModal"
:type="modalType"
:branch-id="branch"
:path="path"
@hide="hideModal"
@create="createTempEntry"
/>
</div> </div>
</template> </template>
<script> <script>
import { __ } from '~/locale'; import { __ } from '~/locale';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { mapActions, mapState } from 'vuex';
import GlModal from '~/vue_shared/components/gl_modal.vue';
export default { export default {
components: { components: {
DeprecatedModal, GlModal,
},
props: {
branchId: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
entryName: this.path !== '' ? `${this.path}/` : '', name: '',
}; };
}, },
computed: { computed: {
...mapState(['newEntryModal']),
entryName: {
get() {
return this.name || (this.newEntryModal.path !== '' ? `${this.newEntryModal.path}/` : '');
},
set(val) {
this.name = val;
},
},
modalTitle() { modalTitle() {
if (this.type === 'tree') { if (this.newEntryModal.type === 'tree') {
return __('Create new directory'); return __('Create new directory');
} }
return __('Create new file'); return __('Create new file');
}, },
buttonLabel() { buttonLabel() {
if (this.type === 'tree') { if (this.newEntryModal.type === 'tree') {
return __('Create directory'); return __('Create directory');
} }
return __('Create file'); return __('Create file');
}, },
}, },
mounted() {
this.$refs.fieldName.focus();
},
methods: { methods: {
...mapActions(['createTempEntry']),
createEntryInStore() { createEntryInStore() {
this.$emit('create', { this.createTempEntry({
branchId: this.branchId, name: this.name,
name: this.entryName, type: this.newEntryModal.type,
type: this.type,
}); });
this.hideModal();
}, },
hideModal() { focusInput() {
this.$emit('hide'); setTimeout(() => {
this.$refs.fieldName.focus();
});
}, },
}, },
}; };
</script> </script>
<template> <template>
<deprecated-modal <gl-modal
:title="modalTitle" id="ide-new-entry"
:primary-button-label="buttonLabel" :header-title-text="modalTitle"
kind="success" :footer-primary-button-text="buttonLabel"
@cancel="hideModal" footer-primary-button-variant="success"
@submit="createEntryInStore" @submit="createEntryInStore"
@open="focusInput"
> >
<form <div
slot="body"
class="form-group row" class="form-group row"
@submit.prevent="createEntryInStore"
> >
<label class="label-light col-form-label col-sm-3"> <label class="label-light col-form-label col-sm-3">
{{ __('Name') }} {{ __('Name') }}
...@@ -85,6 +77,6 @@ export default { ...@@ -85,6 +77,6 @@ export default {
class="form-control" class="form-control"
/> />
</div> </div>
</form> </div>
</deprecated-modal> </gl-modal>
</template> </template>
<script> <script>
export default { import Icon from '~/vue_shared/components/icon.vue';
props: { import ItemButton from './button.vue';
branchId: {
type: String, export default {
required: true, components: {
}, Icon,
path: { ItemButton,
type: String, },
required: false, props: {
default: '', path: {
}, type: String,
required: false,
default: '',
}, },
mounted() { showLabel: {
this.$refs.fileUpload.addEventListener('change', this.openFile); type: Boolean,
required: false,
default: true,
}, },
beforeDestroy() { buttonCssClasses: {
this.$refs.fileUpload.removeEventListener('change', this.openFile); type: String,
required: false,
default: null,
}, },
methods: { },
createFile(target, file, isText) { mounted() {
const { name } = file; this.$refs.fileUpload.addEventListener('change', this.openFile);
let { result } = target; },
beforeDestroy() {
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
methods: {
createFile(target, file, isText) {
const { name } = file;
let { result } = target;
if (!isText) { if (!isText) {
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
result = result.split('base64,')[1]; result = result.split('base64,')[1];
} }
this.$emit('create', { this.$emit('create', {
name: `${(this.path ? `${this.path}/` : '')}${name}`, name: `${this.path ? `${this.path}/` : ''}${name}`,
branchId: this.branchId, type: 'blob',
type: 'blob', content: result,
content: result, base64: !isText,
base64: !isText, });
}); },
}, readFile(file) {
readFile(file) { const reader = new FileReader();
const reader = new FileReader(); const isText = file.type.match(/text.*/) !== null;
const isText = file.type.match(/text.*/) !== null;
reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true }); reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true });
if (isText) { if (isText) {
reader.readAsText(file); reader.readAsText(file);
} else { } else {
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
}, },
openFile() { openFile() {
Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file)); Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
},
startFileUpload() {
this.$refs.fileUpload.click();
},
}, },
}; startFileUpload() {
this.$refs.fileUpload.click();
},
},
};
</script> </script>
<template> <template>
<div> <div>
<a <item-button
href="#" :class="buttonCssClasses"
role="button" :show-label="showLabel"
@click.stop.prevent="startFileUpload" :icon-classes="showLabel ? 'mr-2' : ''"
> :label="__('Upload file')"
{{ __('Upload file') }} class="d-flex"
</a> icon="upload"
@click="startFileUpload"
/>
<input <input
id="file-upload" id="file-upload"
ref="fileUpload" ref="fileUpload"
......
...@@ -40,6 +40,11 @@ export default { ...@@ -40,6 +40,11 @@ export default {
default: false, default: false,
}, },
}, },
data() {
return {
mouseOver: false,
};
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'getChangesInFolder', 'getChangesInFolder',
...@@ -142,6 +147,9 @@ export default { ...@@ -142,6 +147,9 @@ export default {
hasUrlAtCurrentRoute() { hasUrlAtCurrentRoute() {
return this.$router.currentRoute.path === `/project${this.file.url}`; return this.$router.currentRoute.path === `/project${this.file.url}`;
}, },
toggleHover(over) {
this.mouseOver = over;
},
}, },
}; };
</script> </script>
...@@ -153,6 +161,8 @@ export default { ...@@ -153,6 +161,8 @@ export default {
class="file" class="file"
role="button" role="button"
@click="clickFile" @click="clickFile"
@mouseover="toggleHover(true)"
@mouseout="toggleHover(false)"
> >
<div <div
class="file-name" class="file-name"
...@@ -206,6 +216,7 @@ export default { ...@@ -206,6 +216,7 @@ export default {
:project-id="file.projectId" :project-id="file.projectId"
:branch="file.branchId" :branch="file.branchId"
:path="file.path" :path="file.path"
:mouse-over="mouseOver"
class="float-right prepend-left-8" class="float-right prepend-left-8"
/> />
</div> </div>
......
...@@ -52,7 +52,7 @@ export const setResizingStatus = ({ commit }, resizing) => { ...@@ -52,7 +52,7 @@ export const setResizingStatus = ({ commit }, resizing) => {
export const createTempEntry = ( export const createTempEntry = (
{ state, commit, dispatch }, { state, commit, dispatch },
{ branchId, name, type, content = '', base64 = false }, { name, type, content = '', base64 = false },
) => ) =>
new Promise(resolve => { new Promise(resolve => {
const worker = new FilesDecoratorWorker(); const worker = new FilesDecoratorWorker();
...@@ -81,7 +81,7 @@ export const createTempEntry = ( ...@@ -81,7 +81,7 @@ export const createTempEntry = (
commit(types.CREATE_TMP_ENTRY, { commit(types.CREATE_TMP_ENTRY, {
data, data,
projectId: state.currentProjectId, projectId: state.currentProjectId,
branchId, branchId: state.currentBranchId,
}); });
if (type === 'blob') { if (type === 'blob') {
...@@ -100,7 +100,7 @@ export const createTempEntry = ( ...@@ -100,7 +100,7 @@ export const createTempEntry = (
worker.postMessage({ worker.postMessage({
data: [fullName], data: [fullName],
projectId: state.currentProjectId, projectId: state.currentProjectId,
branchId, branchId: state.currentBranchId,
type, type,
tempFile: true, tempFile: true,
base64, base64,
...@@ -178,6 +178,13 @@ export const setLinks = ({ commit }, links) => commit(types.SET_LINKS, links); ...@@ -178,6 +178,13 @@ export const setLinks = ({ commit }, links) => commit(types.SET_LINKS, links);
export const setErrorMessage = ({ commit }, errorMessage) => export const setErrorMessage = ({ commit }, errorMessage) =>
commit(types.SET_ERROR_MESSAGE, errorMessage); commit(types.SET_ERROR_MESSAGE, errorMessage);
export const openNewEntryModal = ({ commit }, { type, path = '' }) => {
commit(types.OPEN_NEW_ENTRY_MODAL, { type, path });
// open the modal manually so we don't mess around with dropdown/rows
$('#ide-new-entry').modal('show');
};
export * from './actions/tree'; export * from './actions/tree';
export * from './actions/file'; export * from './actions/file';
export * from './actions/project'; export * from './actions/project';
......
...@@ -74,3 +74,5 @@ export const CLEAR_PROJECTS = 'CLEAR_PROJECTS'; ...@@ -74,3 +74,5 @@ export const CLEAR_PROJECTS = 'CLEAR_PROJECTS';
export const RESET_OPEN_FILES = 'RESET_OPEN_FILES'; export const RESET_OPEN_FILES = 'RESET_OPEN_FILES';
export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE'; export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE';
export const OPEN_NEW_ENTRY_MODAL = 'OPEN_NEW_ENTRY_MODAL';
...@@ -166,6 +166,11 @@ export default { ...@@ -166,6 +166,11 @@ export default {
[types.SET_ERROR_MESSAGE](state, errorMessage) { [types.SET_ERROR_MESSAGE](state, errorMessage) {
Object.assign(state, { errorMessage }); Object.assign(state, { errorMessage });
}, },
[types.OPEN_NEW_ENTRY_MODAL](state, { type, path }) {
Object.assign(state, {
newEntryModal: { type, path },
});
},
...projectMutations, ...projectMutations,
...mergeRequestMutation, ...mergeRequestMutation,
...fileMutations, ...fileMutations,
......
...@@ -26,4 +26,8 @@ export default () => ({ ...@@ -26,4 +26,8 @@ export default () => ({
rightPane: null, rightPane: null,
links: {}, links: {},
errorMessage: null, errorMessage: null,
newEntryModal: {
type: '',
path: '',
},
}); });
...@@ -45,6 +45,11 @@ export default { ...@@ -45,6 +45,11 @@ export default {
emitSubmit(event) { emitSubmit(event) {
this.$emit('submit', event); this.$emit('submit', event);
}, },
opened({ propertyName }) {
if (propertyName === 'opacity') {
this.$emit('open');
}
},
}, },
}; };
</script> </script>
...@@ -55,6 +60,7 @@ export default { ...@@ -55,6 +60,7 @@ export default {
class="modal fade" class="modal fade"
tabindex="-1" tabindex="-1"
role="dialog" role="dialog"
@transitionend="opened"
> >
<div <div
:class="modalSizeClass" :class="modalSizeClass"
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
padding-bottom: $grid-size; padding-bottom: $grid-size;
.file { .file {
height: 32px;
cursor: pointer; cursor: pointer;
&.file-active { &.file-active {
...@@ -716,32 +717,6 @@ ...@@ -716,32 +717,6 @@
justify-content: center; justify-content: center;
} }
.ide-new-btn {
.btn {
padding-top: 3px;
padding-bottom: 3px;
}
.dropdown {
display: flex;
}
.dropdown-toggle svg {
top: 0;
}
.dropdown-menu {
left: auto;
right: 0;
label {
font-weight: $gl-font-weight-normal;
padding: 5px 8px;
margin-bottom: 0;
}
}
}
.ide { .ide {
overflow: hidden; overflow: hidden;
...@@ -1340,3 +1315,24 @@ ...@@ -1340,3 +1315,24 @@
overflow: auto; overflow: auto;
} }
} }
.ide-entry-dropdown-toggle {
padding: $gl-padding-4;
background-color: $theme-gray-100;
&:hover {
background-color: $theme-gray-200;
}
&:active,
&:focus {
color: $white-normal;
background-color: $blue-500;
outline: 0;
}
}
.ide-new-btn .dropdown.show .ide-entry-dropdown-toggle {
color: $white-normal;
background-color: $blue-500;
}
---
title: Updated design of new entry dropdown in Web IDE
merge_request: 20526
author:
type: changed
...@@ -1720,6 +1720,9 @@ msgstr "" ...@@ -1720,6 +1720,9 @@ msgstr ""
msgid "Create new file" msgid "Create new file"
msgstr "" msgstr ""
msgid "Create new file or directory"
msgstr ""
msgid "Create new label" msgid "Create new label"
msgstr "" msgstr ""
......
...@@ -22,9 +22,7 @@ describe 'Multi-file editor new directory', :js do ...@@ -22,9 +22,7 @@ describe 'Multi-file editor new directory', :js do
end end
it 'creates directory in current directory' do it 'creates directory in current directory' do
find('.add-to-tree').click all('.ide-tree-header button').last.click
click_link('New directory')
page.within('.modal') do page.within('.modal') do
find('.form-control').set('folder name') find('.form-control').set('folder name')
...@@ -32,9 +30,7 @@ describe 'Multi-file editor new directory', :js do ...@@ -32,9 +30,7 @@ describe 'Multi-file editor new directory', :js do
click_button('Create directory') click_button('Create directory')
end end
find('.add-to-tree').click first('.ide-tree-header button').click
click_link('New file')
page.within('.modal-dialog') do page.within('.modal-dialog') do
find('.form-control').set('file name') find('.form-control').set('file name')
......
...@@ -22,9 +22,7 @@ describe 'Multi-file editor new file', :js do ...@@ -22,9 +22,7 @@ describe 'Multi-file editor new file', :js do
end end
it 'creates file in current directory' do it 'creates file in current directory' do
find('.add-to-tree').click first('.ide-tree-header button').click
click_link('New file')
page.within('.modal') do page.within('.modal') do
find('.form-control').set('file name') find('.form-control').set('file name')
......
...@@ -24,14 +24,10 @@ describe 'Multi-file editor upload file', :js do ...@@ -24,14 +24,10 @@ describe 'Multi-file editor upload file', :js do
end end
it 'uploads text file' do it 'uploads text file' do
find('.add-to-tree').click
# make the field visible so capybara can use it # make the field visible so capybara can use it
execute_script('document.querySelector("#file-upload").classList.remove("hidden")') execute_script('document.querySelector("#file-upload").classList.remove("hidden")')
attach_file('file-upload', txt_file) attach_file('file-upload', txt_file)
find('.add-to-tree').click
expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt') expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt')
expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline)) expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline))
end end
......
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import Button from '~/ide/components/new_dropdown/button.vue';
describe('IDE new entry dropdown button component', () => {
let Component;
let vm;
beforeAll(() => {
Component = Vue.extend(Button);
});
beforeEach(() => {
vm = mountComponent(Component, {
label: 'Testing',
icon: 'doc-new',
});
spyOn(vm, '$emit');
});
afterEach(() => {
vm.$destroy();
});
it('renders button with label', () => {
expect(vm.$el.textContent).toContain('Testing');
});
it('renders icon', () => {
expect(vm.$el.querySelector('.ic-doc-new')).not.toBe(null);
});
it('emits click event', () => {
vm.$el.click();
expect(vm.$emit).toHaveBeenCalledWith('click');
});
it('hides label if showLabel is false', done => {
vm.showLabel = false;
vm.$nextTick(() => {
expect(vm.$el.textContent).not.toContain('Testing');
done();
});
});
});
...@@ -13,6 +13,7 @@ describe('new dropdown component', () => { ...@@ -13,6 +13,7 @@ describe('new dropdown component', () => {
vm = createComponentWithStore(component, store, { vm = createComponentWithStore(component, store, {
branch: 'master', branch: 'master',
path: '', path: '',
mouseOver: false,
}); });
vm.$store.state.currentProjectId = 'abcproject'; vm.$store.state.currentProjectId = 'abcproject';
...@@ -21,6 +22,8 @@ describe('new dropdown component', () => { ...@@ -21,6 +22,8 @@ describe('new dropdown component', () => {
tree: [], tree: [],
}; };
spyOn(vm, 'openNewEntryModal');
vm.$mount(); vm.$mount();
}); });
...@@ -31,50 +34,23 @@ describe('new dropdown component', () => { ...@@ -31,50 +34,23 @@ describe('new dropdown component', () => {
}); });
it('renders new file, upload and new directory links', () => { it('renders new file, upload and new directory links', () => {
expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file'); const buttons = vm.$el.querySelectorAll('.dropdown-menu button');
expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file'); expect(buttons[0].textContent.trim()).toBe('New file');
expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory'); expect(buttons[1].textContent.trim()).toBe('Upload file');
expect(buttons[2].textContent.trim()).toBe('New directory');
}); });
describe('createNewItem', () => { describe('createNewItem', () => {
it('sets modalType to blob when new file is clicked', () => { it('sets modalType to blob when new file is clicked', () => {
vm.$el.querySelectorAll('a')[0].click(); vm.$el.querySelectorAll('.dropdown-menu button')[0].click();
expect(vm.modalType).toBe('blob'); expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'blob', path: '' });
}); });
it('sets modalType to tree when new directory is clicked', () => { it('sets modalType to tree when new directory is clicked', () => {
vm.$el.querySelectorAll('a')[2].click(); vm.$el.querySelectorAll('.dropdown-menu button')[2].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('hideModal', () => {
beforeAll(done => {
vm.openModal = true;
Vue.nextTick(done);
});
it('closes modal after toggling', done => {
vm.hideModal();
Vue.nextTick() expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'tree', path: '' });
.then(() => {
expect(vm.$el.querySelector('.modal')).toBeNull();
})
.then(done)
.catch(done.fail);
}); });
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import { createStore } from '~/ide/stores';
import modal from '~/ide/components/new_dropdown/modal.vue'; import modal from '~/ide/components/new_dropdown/modal.vue';
import createComponent from 'spec/helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
describe('new file modal component', () => { describe('new file modal component', () => {
const Component = Vue.extend(modal); const Component = Vue.extend(modal);
...@@ -13,13 +14,15 @@ describe('new file modal component', () => { ...@@ -13,13 +14,15 @@ describe('new file modal component', () => {
['tree', 'blob'].forEach(type => { ['tree', 'blob'].forEach(type => {
describe(type, () => { describe(type, () => {
beforeEach(() => { beforeEach(() => {
vm = createComponent(Component, { const store = createStore();
store.state.newEntryModal = {
type, type,
branchId: 'master',
path: '', path: '',
}); };
vm = createComponentWithStore(Component, store).$mount();
vm.entryName = 'testing'; vm.name = 'testing';
}); });
it(`sets modal title as ${type}`, () => { it(`sets modal title as ${type}`, () => {
...@@ -40,12 +43,11 @@ describe('new file modal component', () => { ...@@ -40,12 +43,11 @@ describe('new file modal component', () => {
describe('createEntryInStore', () => { describe('createEntryInStore', () => {
it('$emits create', () => { it('$emits create', () => {
spyOn(vm, '$emit'); spyOn(vm, 'createTempEntry');
vm.createEntryInStore(); vm.createEntryInStore();
expect(vm.$emit).toHaveBeenCalledWith('create', { expect(vm.createTempEntry).toHaveBeenCalledWith({
branchId: 'master',
name: 'testing', name: 'testing',
type, type,
}); });
...@@ -53,22 +55,4 @@ describe('new file modal component', () => { ...@@ -53,22 +55,4 @@ describe('new file modal component', () => {
}); });
}); });
}); });
it('focuses field on mount', () => {
document.body.innerHTML += '<div class="js-test"></div>';
vm = createComponent(
Component,
{
type: 'tree',
branchId: 'master',
path: '',
},
'.js-test',
);
expect(document.activeElement).toBe(vm.$refs.fieldName);
vm.$el.remove();
});
}); });
...@@ -9,7 +9,6 @@ describe('new dropdown upload', () => { ...@@ -9,7 +9,6 @@ describe('new dropdown upload', () => {
const Component = Vue.extend(upload); const Component = Vue.extend(upload);
vm = createComponent(Component, { vm = createComponent(Component, {
branchId: 'master',
path: '', path: '',
}); });
...@@ -65,7 +64,6 @@ describe('new dropdown upload', () => { ...@@ -65,7 +64,6 @@ describe('new dropdown upload', () => {
expect(vm.$emit).toHaveBeenCalledWith('create', { expect(vm.$emit).toHaveBeenCalledWith('create', {
name: file.name, name: file.name,
branchId: 'master',
type: 'blob', type: 'blob',
content: target.result, content: target.result,
base64: false, base64: false,
...@@ -77,7 +75,6 @@ describe('new dropdown upload', () => { ...@@ -77,7 +75,6 @@ describe('new dropdown upload', () => {
expect(vm.$emit).toHaveBeenCalledWith('create', { expect(vm.$emit).toHaveBeenCalledWith('create', {
name: file.name, name: file.name,
branchId: 'master',
type: 'blob', type: 'blob',
content: binaryTarget.result.split('base64,')[1], content: binaryTarget.result.split('base64,')[1],
base64: true, base64: true,
......
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