Commit 47c906eb authored by Phil Hughes's avatar Phil Hughes

updated karma specs

parent 3811fbff
...@@ -74,7 +74,7 @@ export default { ...@@ -74,7 +74,7 @@ export default {
}, },
watch: { watch: {
activeFile(oldVal, newVal) { activeFile(oldVal, newVal) {
if (!newVal.active) { if (newVal && !newVal.active) {
this.initMonaco(); this.initMonaco();
} }
}, },
......
...@@ -2,9 +2,8 @@ import Vue from 'vue'; ...@@ -2,9 +2,8 @@ import Vue from 'vue';
import flash from '../../flash'; import flash from '../../flash';
import service from '../services'; import service from '../services';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { visitUrl } from '../../lib/utils/url_utility';
export const redirectToUrl = url => visitUrl(url); export const redirectToUrl = url => gl.utils.visitUrl(url);
export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data); export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
...@@ -26,7 +25,7 @@ export const closeAllFiles = ({ state, dispatch }) => { ...@@ -26,7 +25,7 @@ export const closeAllFiles = ({ state, dispatch }) => {
state.openFiles.forEach(file => dispatch('closeFile', { file })); state.openFiles.forEach(file => dispatch('closeFile', { file }));
}; };
export const toggleEditMode = ({ commit, getters, dispatch }, force = false) => { export const toggleEditMode = ({ state, commit, getters, dispatch }, force = false) => {
const changedFiles = getters.changedFiles; const changedFiles = getters.changedFiles;
if (changedFiles.length && !force) { if (changedFiles.length && !force) {
...@@ -35,7 +34,10 @@ export const toggleEditMode = ({ commit, getters, dispatch }, force = false) => ...@@ -35,7 +34,10 @@ export const toggleEditMode = ({ commit, getters, dispatch }, force = false) =>
commit(types.TOGGLE_EDIT_MODE); commit(types.TOGGLE_EDIT_MODE);
commit(types.TOGGLE_DISCARD_POPUP, false); commit(types.TOGGLE_DISCARD_POPUP, false);
dispatch('toggleBlobView'); dispatch('toggleBlobView');
dispatch('discardAllChanges');
if (!state.editMode) {
dispatch('discardAllChanges');
}
} }
}; };
...@@ -111,9 +113,12 @@ export const popHistoryState = ({ state, dispatch, getters }) => { ...@@ -111,9 +113,12 @@ export const popHistoryState = ({ state, dispatch, getters }) => {
export const scrollToTab = () => { export const scrollToTab = () => {
Vue.nextTick(() => { Vue.nextTick(() => {
const tabs = document.getElementById('tabs'); const tabs = document.getElementById('tabs');
const tabEl = tabs.querySelector('.active');
tabs.scrollLeft = tabEl.offsetLeft; if (tabs) {
const tabEl = tabs.querySelector('.active');
tabs.scrollLeft = tabEl.offsetLeft;
}
}); });
}; };
......
...@@ -3,6 +3,7 @@ import flash from '../../../flash'; ...@@ -3,6 +3,7 @@ import flash from '../../../flash';
import service from '../../services'; import service from '../../services';
import * as types from '../mutation_types'; import * as types from '../mutation_types';
import { import {
findEntry,
pushState, pushState,
setPageTitle, setPageTitle,
createTemp, createTemp,
...@@ -88,11 +89,16 @@ export const createTempFile = ({ state, commit, dispatch }, { tree, name }) => { ...@@ -88,11 +89,16 @@ export const createTempFile = ({ state, commit, dispatch }, { tree, name }) => {
changed: true, changed: true,
}); });
if (findEntry(tree, 'blob', file.name)) return;
commit(types.CREATE_TMP_FILE, { commit(types.CREATE_TMP_FILE, {
parent: tree, parent: tree,
file, file,
}); });
commit(types.TOGGLE_FILE_OPEN, file); commit(types.TOGGLE_FILE_OPEN, file);
dispatch('setFileActive', file); dispatch('setFileActive', file);
dispatch('toggleEditMode', true);
if (!state.editMode) {
dispatch('toggleEditMode', true);
}
}; };
...@@ -76,7 +76,7 @@ export const clickedTreeRow = ({ commit, dispatch }, row) => { ...@@ -76,7 +76,7 @@ export const clickedTreeRow = ({ commit, dispatch }, row) => {
export const createTempTree = ({ state, commit, dispatch }, name) => { export const createTempTree = ({ state, commit, dispatch }, name) => {
let tree = state; let tree = state;
const dirNames = name.replace(`${state.path}/`, '').split('/'); const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/');
dirNames.forEach((dirName) => { dirNames.forEach((dirName) => {
const foundEntry = findEntry(tree, 'tree', dirName); const foundEntry = findEntry(tree, 'tree', dirName);
...@@ -101,8 +101,10 @@ export const createTempTree = ({ state, commit, dispatch }, name) => { ...@@ -101,8 +101,10 @@ export const createTempTree = ({ state, commit, dispatch }, name) => {
} }
}); });
dispatch('createTempFile', { if (tree.tempFile) {
tree, dispatch('createTempFile', {
name: '.gitkeep', tree,
}); name: '.gitkeep',
});
}
}; };
...@@ -15,11 +15,7 @@ export const changedFiles = (state) => { ...@@ -15,11 +15,7 @@ export const changedFiles = (state) => {
return files.filter(file => file.changed); return files.filter(file => file.changed);
}; };
export const activeFile = (state) => { export const activeFile = state => state.openFiles.find(file => file.active);
const openedFiles = state.openFiles;
return openedFiles.find(file => file.active);
};
export const activeFileExtension = (state) => { export const activeFileExtension = (state) => {
const file = activeFile(state); const file = activeFile(state);
......
...@@ -8,7 +8,7 @@ import mutations from './mutations'; ...@@ -8,7 +8,7 @@ import mutations from './mutations';
Vue.use(Vuex); Vue.use(Vuex);
export default new Vuex.Store({ export default new Vuex.Store({
state, state: state(),
actions, actions,
mutations, mutations,
getters, getters,
......
export default { export default () => ({
project: { project: {
id: 0, id: 0,
name: '', name: '',
...@@ -20,4 +20,4 @@ export default { ...@@ -20,4 +20,4 @@ export default {
openFiles: [], openFiles: [],
parentTreeUrl: '', parentTreeUrl: '',
previousUrl: '', previousUrl: '',
}; });
...@@ -36,8 +36,8 @@ export const decorateData = (entity, projectUrl = '') => { ...@@ -36,8 +36,8 @@ export const decorateData = (entity, projectUrl = '') => {
last_commit, last_commit,
tree_url, tree_url,
path, path,
tempFile,
renderError, renderError,
tempFile = false,
active = false, active = false,
opened = false, opened = false,
changed = false, changed = false,
......
export const createComponentWithStore = (Component, store, propsData = {}) => new Component({
store,
propsData,
});
export default (Component, props = {}, el = null) => new Component({ export default (Component, props = {}, el = null) => new Component({
propsData: props, propsData: props,
}).$mount(el); }).$mount(el);
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import newBranchForm from '~/repo/components/new_branch_form.vue'; import newBranchForm from '~/repo/components/new_branch_form.vue';
import eventHub from '~/repo/event_hub'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import RepoStore from '~/repo/stores/repo_store'; import { resetStore } from '../helpers';
import createComponent from '../../helpers/vue_mount_component_helper';
describe('Multi-file editor new branch form', () => { describe('Multi-file editor new branch form', () => {
let vm; let vm;
...@@ -10,17 +10,17 @@ describe('Multi-file editor new branch form', () => { ...@@ -10,17 +10,17 @@ describe('Multi-file editor new branch form', () => {
beforeEach(() => { beforeEach(() => {
const Component = Vue.extend(newBranchForm); const Component = Vue.extend(newBranchForm);
RepoStore.currentBranch = 'master'; vm = createComponentWithStore(Component, store);
vm = createComponent(Component, { vm.$store.state.currentBranch = 'master';
currentBranch: RepoStore.currentBranch,
}); vm.$mount();
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
RepoStore.currentBranch = ''; resetStore(vm.$store);
}); });
describe('template', () => { describe('template', () => {
...@@ -48,6 +48,10 @@ describe('Multi-file editor new branch form', () => { ...@@ -48,6 +48,10 @@ describe('Multi-file editor new branch form', () => {
}); });
describe('submitNewBranch', () => { describe('submitNewBranch', () => {
beforeEach(() => {
spyOn(vm, 'createNewBranch').and.returnValue(Promise.resolve());
});
it('sets to loading', () => { it('sets to loading', () => {
vm.submitNewBranch(); vm.submitNewBranch();
...@@ -66,57 +70,45 @@ describe('Multi-file editor new branch form', () => { ...@@ -66,57 +70,45 @@ describe('Multi-file editor new branch form', () => {
}); });
}); });
it('emits an event with branchName', () => { it('calls createdNewBranch with branchName', () => {
spyOn(eventHub, '$emit');
vm.branchName = 'testing'; vm.branchName = 'testing';
vm.submitNewBranch(); vm.submitNewBranch();
expect(eventHub.$emit).toHaveBeenCalledWith('createNewBranch', 'testing'); expect(vm.createNewBranch).toHaveBeenCalledWith('testing');
}); });
}); });
describe('showErrorMessage', () => { describe('submitNewBranch with error', () => {
it('sets loading to false', () => { beforeEach(() => {
vm.loading = true; spyOn(vm, 'createNewBranch').and.returnValue(Promise.reject({
json: () => Promise.resolve({
vm.showErrorMessage(); message: 'error message',
}),
expect(vm.loading).toBeFalsy(); }));
});
it('creates flash element', () => {
vm.showErrorMessage('error message');
expect(vm.$el.querySelector('.flash-alert')).not.toBeNull();
expect(vm.$el.querySelector('.flash-alert').textContent.trim()).toBe('error message');
}); });
});
describe('createdNewBranch', () => { it('sets loading to false', (done) => {
it('set loading to false', () => {
vm.loading = true; vm.loading = true;
vm.createdNewBranch(); vm.submitNewBranch();
expect(vm.loading).toBeFalsy();
});
it('resets branch name', () => {
vm.branchName = 'testing';
vm.createdNewBranch(); setTimeout(() => {
expect(vm.loading).toBeFalsy();
expect(vm.branchName).toBe(''); done();
});
}); });
it('sets the dropdown toggle text', () => { it('creates flash element', (done) => {
vm.dropdownText = document.createElement('span'); vm.submitNewBranch();
vm.createdNewBranch('branch name'); setTimeout(() => {
expect(vm.$el.querySelector('.flash-alert')).not.toBeNull();
expect(vm.$el.querySelector('.flash-alert').textContent.trim()).toBe('error message');
expect(vm.dropdownText.textContent).toBe('branch name'); done();
});
}); });
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import newDropdown from '~/repo/components/new_dropdown/index.vue'; import newDropdown from '~/repo/components/new_dropdown/index.vue';
import RepoStore from '~/repo/stores/repo_store'; import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import RepoHelper from '~/repo/helpers/repo_helper'; import { resetStore } from '../../helpers';
import eventHub from '~/repo/event_hub';
import createComponent from '../../../helpers/vue_mount_component_helper';
describe('new dropdown component', () => { describe('new dropdown component', () => {
let vm; let vm;
...@@ -11,15 +10,13 @@ describe('new dropdown component', () => { ...@@ -11,15 +10,13 @@ describe('new dropdown component', () => {
beforeEach(() => { beforeEach(() => {
const component = Vue.extend(newDropdown); const component = Vue.extend(newDropdown);
vm = createComponent(component); vm = createComponentWithStore(component, store).$mount();
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
RepoStore.files = []; resetStore(vm.$store);
RepoStore.openedFiles = [];
RepoStore.setViewToPreview();
}); });
it('renders new file and new directory links', () => { it('renders new file and new directory links', () => {
...@@ -67,125 +64,4 @@ describe('new dropdown component', () => { ...@@ -67,125 +64,4 @@ describe('new dropdown component', () => {
.catch(done.fail); .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);
});
});
});
}); });
import Vue from 'vue'; import Vue from 'vue';
import RepoStore from '~/repo/stores/repo_store'; import store from '~/repo/stores';
import modal from '~/repo/components/new_dropdown/modal.vue'; import modal from '~/repo/components/new_dropdown/modal.vue';
import eventHub from '~/repo/event_hub'; import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import createComponent from '../../../helpers/vue_mount_component_helper'; import { file, resetStore } from '../../helpers';
describe('new file modal component', () => { describe('new file modal component', () => {
const Component = Vue.extend(modal); const Component = Vue.extend(modal);
...@@ -11,18 +11,17 @@ describe('new file modal component', () => { ...@@ -11,18 +11,17 @@ describe('new file modal component', () => {
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
RepoStore.files = []; resetStore(vm.$store);
RepoStore.openedFiles = [];
RepoStore.setViewToPreview();
}); });
['tree', 'blob'].forEach((type) => { ['tree', 'blob'].forEach((type) => {
describe(type, () => { describe(type, () => {
beforeEach(() => { beforeEach(() => {
vm = createComponent(Component, { vm = createComponentWithStore(Component, store, {
type, type,
currentPath: RepoStore.path, }).$mount();
});
vm.entryName = 'testing';
}); });
it(`sets modal title as ${type}`, () => { it(`sets modal title as ${type}`, () => {
...@@ -42,35 +41,156 @@ describe('new file modal component', () => { ...@@ -42,35 +41,156 @@ describe('new file modal component', () => {
expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(`${title} name`); expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(`${title} name`);
}); });
describe('createEntryInStore', () => {
it('calls createTempEntry', () => {
spyOn(vm, 'createTempEntry');
vm.createEntryInStore();
expect(vm.createTempEntry).toHaveBeenCalledWith({
name: 'testing',
type,
});
});
it('sets editMode to true', (done) => {
vm.createEntryInStore();
setTimeout(() => {
expect(vm.$store.state.editMode).toBeTruthy();
done();
});
});
it('toggles blob view', (done) => {
vm.createEntryInStore();
setTimeout(() => {
expect(vm.$store.state.currentBlobView).toBe('repo-editor');
done();
});
});
it('opens newly created file', (done) => {
vm.createEntryInStore();
setTimeout(() => {
expect(vm.$store.state.openFiles.length).toBe(1);
expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep');
done();
});
});
it(`creates ${type} in the current stores path`, (done) => {
vm.$store.state.path = 'app';
vm.createEntryInStore();
setTimeout(() => {
expect(vm.$store.state.tree[0].path).toBe('app/testing');
expect(vm.$store.state.tree[0].name).toBe('testing');
if (type === 'tree') {
expect(vm.$store.state.tree[0].tree.length).toBe(1);
}
done();
});
});
if (type === 'blob') {
it('creates new file', (done) => {
vm.createEntryInStore();
setTimeout(() => {
expect(vm.$store.state.tree.length).toBe(1);
expect(vm.$store.state.tree[0].name).toBe('testing');
expect(vm.$store.state.tree[0].type).toBe('blob');
expect(vm.$store.state.tree[0].tempFile).toBeTruthy();
done();
});
});
it('does not create temp file when file already exists', (done) => {
vm.$store.state.tree.push(file('testing', '1', type));
vm.createEntryInStore();
setTimeout(() => {
expect(vm.$store.state.tree.length).toBe(1);
expect(vm.$store.state.tree[0].name).toBe('testing');
expect(vm.$store.state.tree[0].type).toBe('blob');
expect(vm.$store.state.tree[0].tempFile).toBeFalsy();
done();
});
});
} else {
it('creates new tree', () => {
vm.createEntryInStore();
expect(vm.$store.state.tree.length).toBe(1);
expect(vm.$store.state.tree[0].name).toBe('testing');
expect(vm.$store.state.tree[0].type).toBe('tree');
expect(vm.$store.state.tree[0].tempFile).toBeTruthy();
expect(vm.$store.state.tree[0].tree.length).toBe(1);
expect(vm.$store.state.tree[0].tree[0].name).toBe('.gitkeep');
});
it('creates multiple trees when entryName has slashes', () => {
vm.entryName = 'app/test';
vm.createEntryInStore();
expect(vm.$store.state.tree.length).toBe(1);
expect(vm.$store.state.tree[0].name).toBe('app');
expect(vm.$store.state.tree[0].tree[0].name).toBe('test');
expect(vm.$store.state.tree[0].tree[0].tree[0].name).toBe('.gitkeep');
});
it('creates tree in existing tree', () => {
vm.$store.state.tree.push(file('app', '1', 'tree'));
vm.entryName = 'app/test';
vm.createEntryInStore();
expect(vm.$store.state.tree.length).toBe(1);
expect(vm.$store.state.tree[0].name).toBe('app');
expect(vm.$store.state.tree[0].tempFile).toBeFalsy();
expect(vm.$store.state.tree[0].tree[0].tempFile).toBeTruthy();
expect(vm.$store.state.tree[0].tree[0].name).toBe('test');
expect(vm.$store.state.tree[0].tree[0].tree[0].name).toBe('.gitkeep');
});
it('does not create new tree when already exists', () => {
vm.$store.state.tree.push(file('app', '1', 'tree'));
vm.entryName = 'app';
vm.createEntryInStore();
expect(vm.$store.state.tree.length).toBe(1);
expect(vm.$store.state.tree[0].name).toBe('app');
expect(vm.$store.state.tree[0].tempFile).toBeFalsy();
expect(vm.$store.state.tree[0].tree.length).toBe(0);
});
}
});
}); });
}); });
it('focuses field on mount', () => { it('focuses field on mount', () => {
document.body.innerHTML += '<div class="js-test"></div>'; document.body.innerHTML += '<div class="js-test"></div>';
vm = createComponent(Component, { vm = createComponentWithStore(Component, store, {
type: 'tree', type: 'tree',
currentPath: RepoStore.path, }).$mount('.js-test');
}, '.js-test');
expect(document.activeElement).toBe(vm.$refs.fieldName); expect(document.activeElement).toBe(vm.$refs.fieldName);
vm.$el.remove(); 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');
});
});
}); });
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import service from '~/repo/services';
import repoCommitSection from '~/repo/components/repo_commit_section.vue'; import repoCommitSection from '~/repo/components/repo_commit_section.vue';
import RepoStore from '~/repo/stores/repo_store';
import RepoService from '~/repo/services/repo_service';
import getSetTimeoutPromise from '../../helpers/set_timeout_promise_helper'; import getSetTimeoutPromise from '../../helpers/set_timeout_promise_helper';
import { file, resetStore } from '../helpers';
describe('RepoCommitSection', () => { describe('RepoCommitSection', () => {
const branch = 'master'; let vm;
const projectUrl = 'projectUrl';
let changedFiles;
let openedFiles;
RepoStore.projectUrl = projectUrl; function createComponent() {
function createComponent(el) {
const RepoCommitSection = Vue.extend(repoCommitSection); const RepoCommitSection = Vue.extend(repoCommitSection);
return new RepoCommitSection().$mount(el); const comp = new RepoCommitSection({
store,
}).$mount();
comp.$store.state.currentBranch = 'master';
comp.$store.state.openFiles = [file(), file()];
comp.$store.state.openFiles.forEach(f => Object.assign(f, {
changed: true,
content: 'testing',
}));
return comp.$mount();
} }
beforeEach(() => { beforeEach(() => {
// Create a copy for each test because these can get modified directly vm = createComponent();
changedFiles = [{
id: 0,
changed: true,
url: `/namespace/${projectUrl}/blob/${branch}/dir/file0.ext`,
path: 'dir/file0.ext',
newContent: 'a',
}, {
id: 1,
changed: true,
url: `/namespace/${projectUrl}/blob/${branch}/dir/file1.ext`,
path: 'dir/file1.ext',
newContent: 'b',
}];
openedFiles = changedFiles.concat([{
id: 2,
url: `/namespace/${projectUrl}/blob/${branch}/dir/file2.ext`,
path: 'dir/file2.ext',
changed: false,
}]);
}); });
it('renders a commit section', () => { afterEach(() => {
RepoStore.isCommitable = true; vm.$destroy();
RepoStore.currentBranch = branch;
RepoStore.targetBranch = branch; resetStore(vm.$store);
RepoStore.openedFiles = openedFiles; });
const vm = createComponent(); it('renders a commit section', () => {
const changedFileElements = [...vm.$el.querySelectorAll('.changed-files > li')]; const changedFileElements = [...vm.$el.querySelectorAll('.changed-files > li')];
const commitMessage = vm.$el.querySelector('#commit-message'); const submitCommit = vm.$el.querySelector('.btn');
const submitCommit = vm.$refs.submitCommit;
const targetBranch = vm.$el.querySelector('.target-branch'); const targetBranch = vm.$el.querySelector('.target-branch');
expect(vm.$el.querySelector(':scope > form')).toBeTruthy(); expect(vm.$el.querySelector(':scope > form')).toBeTruthy();
...@@ -58,160 +45,70 @@ describe('RepoCommitSection', () => { ...@@ -58,160 +45,70 @@ describe('RepoCommitSection', () => {
expect(changedFileElements.length).toEqual(2); expect(changedFileElements.length).toEqual(2);
changedFileElements.forEach((changedFile, i) => { changedFileElements.forEach((changedFile, i) => {
expect(changedFile.textContent.trim()).toEqual(changedFiles[i].path); expect(changedFile.textContent.trim()).toEqual(vm.$store.getters.changedFiles[i].path);
}); });
expect(commitMessage.tagName).toEqual('TEXTAREA');
expect(commitMessage.name).toEqual('commit-message');
expect(submitCommit.type).toEqual('submit');
expect(submitCommit.disabled).toBeTruthy(); expect(submitCommit.disabled).toBeTruthy();
expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy(); expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy();
expect(vm.$el.querySelector('.commit-summary').textContent.trim()).toEqual('Commit 2 files'); expect(vm.$el.querySelector('.commit-summary').textContent.trim()).toEqual('Commit 2 files');
expect(targetBranch.querySelector(':scope > label').textContent.trim()).toEqual('Target branch'); expect(targetBranch.querySelector(':scope > label').textContent.trim()).toEqual('Target branch');
expect(targetBranch.querySelector('.help-block').textContent.trim()).toEqual(branch); expect(targetBranch.querySelector('.help-block').textContent.trim()).toEqual('master');
});
it('does not render if not isCommitable', () => {
RepoStore.isCommitable = false;
RepoStore.openedFiles = [{
id: 0,
changed: true,
}];
const vm = createComponent();
expect(vm.$el.innerHTML).toBeFalsy();
});
it('does not render if no changedFiles', () => {
RepoStore.isCommitable = true;
RepoStore.openedFiles = [];
const vm = createComponent();
expect(vm.$el.innerHTML).toBeFalsy();
}); });
describe('when submitting', () => { describe('when submitting', () => {
let el; let changedFiles;
let vm;
const projectId = 'projectId';
const commitMessage = 'commitMessage';
beforeEach((done) => {
RepoStore.isCommitable = true;
RepoStore.currentBranch = branch;
RepoStore.targetBranch = branch;
RepoStore.openedFiles = openedFiles;
RepoStore.projectId = projectId;
// We need to append to body to get form `submit` events working
// Otherwise we run into, "Form submission canceled because the form is not connected"
// See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
el = document.createElement('div');
document.body.appendChild(el);
vm = createComponent(el);
vm.commitMessage = commitMessage;
spyOn(vm, 'tryCommit').and.callThrough();
spyOn(vm, 'redirectToNewMr').and.stub();
spyOn(vm, 'redirectToBranch').and.stub();
spyOn(RepoService, 'commitFiles').and.returnValue(Promise.resolve());
spyOn(RepoService, 'getBranch').and.returnValue(Promise.resolve({
commit: {
id: 1,
short_id: 1,
},
}));
// Wait for the vm data to be in place
Vue.nextTick(() => {
done();
});
});
afterEach(() => { beforeEach(() => {
vm.$destroy(); vm.commitMessage = 'testing';
el.remove(); changedFiles = JSON.parse(JSON.stringify(vm.$store.getters.changedFiles));
RepoStore.openedFiles = [];
});
it('shows commit message', () => { spyOn(service, 'commit').and.returnValue(Promise.resolve({
const commitMessageEl = vm.$el.querySelector('#commit-message'); short_id: '1',
expect(commitMessageEl.value).toBe(commitMessage); stats: {},
}));
}); });
it('allows you to submit', () => { it('allows you to submit', () => {
const submitCommit = vm.$refs.submitCommit; expect(vm.$el.querySelector('.btn').disabled).toBeTruthy();
expect(submitCommit.disabled).toBeFalsy();
}); });
it('shows commit submit and summary if commitMessage and spinner if submitCommitsLoading', (done) => { it('submits commit', (done) => {
const submitCommit = vm.$refs.submitCommit; vm.makeCommit();
submitCommit.click();
// Wait for the branch check to finish // Wait for the branch check to finish
getSetTimeoutPromise() getSetTimeoutPromise()
.then(() => Vue.nextTick()) .then(() => Vue.nextTick())
.then(() => { .then(() => {
expect(vm.tryCommit).toHaveBeenCalled(); const args = service.commit.calls.allArgs()[0];
expect(submitCommit.querySelector('.js-commit-loading-icon')).toBeTruthy(); const { commit_message, actions, branch: payloadBranch } = args[1];
expect(vm.redirectToBranch).toHaveBeenCalled();
const args = RepoService.commitFiles.calls.allArgs()[0];
const { commit_message, actions, branch: payloadBranch } = args[0];
expect(commit_message).toBe(commitMessage); expect(commit_message).toBe('testing');
expect(actions.length).toEqual(2); expect(actions.length).toEqual(2);
expect(payloadBranch).toEqual(branch); expect(payloadBranch).toEqual('master');
expect(actions[0].action).toEqual('update'); expect(actions[0].action).toEqual('update');
expect(actions[1].action).toEqual('update'); expect(actions[1].action).toEqual('update');
expect(actions[0].content).toEqual(openedFiles[0].newContent); expect(actions[0].content).toEqual(changedFiles[0].content);
expect(actions[1].content).toEqual(openedFiles[1].newContent); expect(actions[1].content).toEqual(changedFiles[1].content);
expect(actions[0].file_path).toEqual(openedFiles[0].path); expect(actions[0].file_path).toEqual(changedFiles[0].path);
expect(actions[1].file_path).toEqual(openedFiles[1].path); expect(actions[1].file_path).toEqual(changedFiles[1].path);
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
it('redirects to MR creation page if start new MR checkbox checked', (done) => { it('redirects to MR creation page if start new MR checkbox checked', (done) => {
spyOn(gl.utils, 'visitUrl');
vm.startNewMR = true; vm.startNewMR = true;
Vue.nextTick() vm.makeCommit();
.then(() => {
const submitCommit = vm.$refs.submitCommit; getSetTimeoutPromise()
submitCommit.click(); .then(() => Vue.nextTick())
})
// Wait for the branch check to finish
.then(() => getSetTimeoutPromise())
.then(() => { .then(() => {
expect(vm.redirectToNewMr).toHaveBeenCalled(); expect(gl.utils.visitUrl).toHaveBeenCalled();
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
}); });
describe('methods', () => {
describe('resetCommitState', () => {
it('should reset store vars and scroll to top', () => {
const vm = {
submitCommitsLoading: true,
changedFiles: new Array(10),
openedFiles: new Array(3),
commitMessage: 'commitMessage',
editMode: true,
};
repoCommitSection.methods.resetCommitState.call(vm);
expect(vm.submitCommitsLoading).toEqual(false);
expect(vm.changedFiles).toEqual([]);
expect(vm.commitMessage).toEqual('');
expect(vm.editMode).toEqual(false);
});
});
});
}); });
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import repoEditButton from '~/repo/components/repo_edit_button.vue'; import repoEditButton from '~/repo/components/repo_edit_button.vue';
import RepoStore from '~/repo/stores/repo_store'; import { file, resetStore } from '../helpers';
describe('RepoEditButton', () => { describe('RepoEditButton', () => {
function createComponent() { let vm;
beforeEach(() => {
const f = file();
const RepoEditButton = Vue.extend(repoEditButton); const RepoEditButton = Vue.extend(repoEditButton);
return new RepoEditButton().$mount(); vm = new RepoEditButton({
} store,
});
f.active = true;
f.changed = true;
vm.$store.dispatch('setInitialData', {
canCommit: true,
onTopOfBranch: true,
});
vm.$store.state.openFiles.push(f);
});
afterEach(() => { afterEach(() => {
RepoStore.openedFiles = []; vm.$destroy();
resetStore(vm.$store);
}); });
it('renders an edit button that toggles the view state', (done) => { it('renders an edit button', () => {
RepoStore.isCommitable = true; vm.$mount();
RepoStore.changedFiles = [];
RepoStore.binary = false;
RepoStore.openedFiles = [{}, {}];
const vm = createComponent(); expect(vm.$el.querySelector('.btn')).not.toBeNull();
expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit');
});
expect(vm.$el.tagName).toEqual('BUTTON'); it('renders edit button with cancel text', () => {
expect(vm.$el.textContent).toMatch('Edit'); vm.$store.state.editMode = true;
spyOn(vm, 'editCancelClicked').and.callThrough(); vm.$mount();
vm.$el.click(); expect(vm.$el.querySelector('.btn')).not.toBeNull();
expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
});
Vue.nextTick(() => { it('toggles edit mode on click', () => {
expect(vm.editCancelClicked).toHaveBeenCalled(); vm.$mount();
expect(vm.$el.textContent).toMatch('Cancel edit');
done(); vm.$el.querySelector('.btn').click();
});
expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
}); });
it('does not render if not isCommitable', () => { describe('discardPopupOpen', () => {
RepoStore.isCommitable = false; beforeEach(() => {
vm.$store.state.discardPopupOpen = true;
vm.$mount();
});
it('renders popup', () => {
expect(vm.$el.querySelector('.modal')).not.toBeNull();
});
it('removes all changed files', (done) => {
vm.$el.querySelector('.btn-warning').click();
const vm = createComponent(); vm.$nextTick(() => {
expect(vm.$store.getters.changedFiles.length).toBe(0);
expect(vm.$el.querySelector('.modal')).toBeNull();
expect(vm.$el.innerHTML).toBeUndefined(); done();
});
});
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import RepoStore from '~/repo/stores/repo_store'; import store from '~/repo/stores';
import repoEditor from '~/repo/components/repo_editor.vue'; import repoEditor from '~/repo/components/repo_editor.vue';
import { file, resetStore } from '../helpers';
describe('RepoEditor', () => { describe('RepoEditor', () => {
let vm;
beforeEach(() => { beforeEach(() => {
const f = file();
const RepoEditor = Vue.extend(repoEditor); const RepoEditor = Vue.extend(repoEditor);
this.vm = new RepoEditor().$mount(); vm = new RepoEditor({
store,
});
f.active = true;
f.tempFile = true;
vm.$store.state.openFiles.push(f);
vm.monaco = true;
vm.$mount();
}); });
afterEach(() => { afterEach(() => {
RepoStore.openedFiles = []; vm.$destroy();
resetStore(vm.$store);
}); });
it('renders an ide container', (done) => { it('renders an ide container', (done) => {
this.vm.openedFiles = ['idiidid'];
this.vm.binary = false;
Vue.nextTick(() => { Vue.nextTick(() => {
expect(this.vm.shouldHideEditor).toBe(false); expect(vm.shouldHideEditor).toBeFalsy();
expect(this.vm.$el.id).toEqual('ide');
expect(this.vm.$el.tagName).toBe('DIV');
done(); done();
}); });
}); });
describe('when there are no open files', () => {
it('does not render the ide', (done) => {
this.vm.openedFiles = [];
Vue.nextTick(() => {
expect(this.vm.shouldHideEditor).toBe(true);
expect(this.vm.$el.tagName).not.toBeDefined();
done();
});
});
});
describe('when open file is binary and not raw', () => { describe('when open file is binary and not raw', () => {
it('does not render the IDE', (done) => { it('does not render the IDE', (done) => {
this.vm.binary = true; vm.$store.getters.activeFile.binary = true;
this.vm.activeFile = {
raw: false,
};
Vue.nextTick(() => { Vue.nextTick(() => {
expect(this.vm.shouldHideEditor).toBe(true); expect(vm.shouldHideEditor).toBeTruthy();
expect(this.vm.$el.tagName).not.toBeDefined();
done(); done();
}); });
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import repoFileButtons from '~/repo/components/repo_file_buttons.vue'; import repoFileButtons from '~/repo/components/repo_file_buttons.vue';
import RepoStore from '~/repo/stores/repo_store'; import { file, resetStore } from '../helpers';
describe('RepoFileButtons', () => { describe('RepoFileButtons', () => {
const activeFile = { const activeFile = file();
extension: 'md', let vm;
url: 'url',
raw_path: 'raw_path',
blame_path: 'blame_path',
commits_path: 'commits_path',
permalink: 'permalink',
};
function createComponent() { function createComponent() {
const RepoFileButtons = Vue.extend(repoFileButtons); const RepoFileButtons = Vue.extend(repoFileButtons);
return new RepoFileButtons().$mount(); activeFile.rawPath = 'test';
activeFile.blamePath = 'test';
activeFile.commitsPath = 'test';
activeFile.active = true;
store.state.openFiles.push(activeFile);
return new RepoFileButtons({
store,
}).$mount();
} }
afterEach(() => { afterEach(() => {
RepoStore.openedFiles = []; vm.$destroy();
});
it('renders Raw, Blame, History, Permalink and Preview toggle', () => {
const activeFileLabel = 'activeFileLabel';
RepoStore.openedFiles = new Array(1);
RepoStore.activeFile = activeFile;
RepoStore.activeFileLabel = activeFileLabel;
RepoStore.editMode = true;
RepoStore.binary = false;
const vm = createComponent(); resetStore(vm.$store);
const raw = vm.$el.querySelector('.raw');
const blame = vm.$el.querySelector('.blame');
const history = vm.$el.querySelector('.history');
expect(raw.href).toMatch(`/${activeFile.raw_path}`);
expect(raw.textContent.trim()).toEqual('Raw');
expect(blame.href).toMatch(`/${activeFile.blame_path}`);
expect(blame.textContent.trim()).toEqual('Blame');
expect(history.href).toMatch(`/${activeFile.commits_path}`);
expect(history.textContent.trim()).toEqual('History');
expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink');
expect(vm.$el.querySelector('.preview').textContent.trim()).toEqual(activeFileLabel);
}); });
it('triggers rawPreviewToggle on preview click', () => { it('renders Raw, Blame, History, Permalink and Preview toggle', (done) => {
RepoStore.openedFiles = new Array(1); vm = createComponent();
RepoStore.activeFile = activeFile;
RepoStore.editMode = true;
const vm = createComponent();
const preview = vm.$el.querySelector('.preview');
spyOn(vm, 'rawPreviewToggle');
preview.click();
expect(vm.rawPreviewToggle).toHaveBeenCalled();
});
it('does not render preview toggle if not canPreview', () => { vm.$nextTick(() => {
activeFile.extension = 'js'; const raw = vm.$el.querySelector('.raw');
RepoStore.openedFiles = new Array(1); const blame = vm.$el.querySelector('.blame');
RepoStore.activeFile = activeFile; const history = vm.$el.querySelector('.history');
const vm = createComponent(); expect(raw.href).toMatch(`/${activeFile.rawPath}`);
expect(raw.textContent.trim()).toEqual('Raw');
expect(blame.href).toMatch(`/${activeFile.blamePath}`);
expect(blame.textContent.trim()).toEqual('Blame');
expect(history.href).toMatch(`/${activeFile.commitsPath}`);
expect(history.textContent.trim()).toEqual('History');
expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink');
expect(vm.$el.querySelector('.preview')).toBeFalsy(); done();
});
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import repoFile from '~/repo/components/repo_file.vue'; import repoFile from '~/repo/components/repo_file.vue';
import RepoStore from '~/repo/stores/repo_store'; import { file, resetStore } from '../helpers';
import eventHub from '~/repo/event_hub';
import { file } from '../mock_data';
describe('RepoFile', () => { describe('RepoFile', () => {
const updated = 'updated'; const updated = 'updated';
const otherFile = {
id: 'test',
html: '<p class="file-content">html</p>',
pageTitle: 'otherpageTitle',
};
function createComponent(propsData) { function createComponent(propsData) {
const RepoFile = Vue.extend(repoFile); const RepoFile = Vue.extend(repoFile);
return new RepoFile({ return new RepoFile({
store,
propsData, propsData,
}).$mount(); }).$mount();
} }
beforeEach(() => { afterEach(() => {
RepoStore.openedFiles = []; resetStore(vm.$store);
}); });
it('renders link, icon, name and last commit details', () => { it('renders link, icon, name and last commit details', () => {
const RepoFile = Vue.extend(repoFile); const RepoFile = Vue.extend(repoFile);
const vm = new RepoFile({ const vm = new RepoFile({
store,
propsData: { propsData: {
file: file(), file: file(),
}, },
...@@ -54,12 +50,6 @@ describe('RepoFile', () => { ...@@ -54,12 +50,6 @@ describe('RepoFile', () => {
expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy(); expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy();
}); });
it('sets the document title correctly', () => {
RepoStore.setActiveFiles(otherFile);
expect(document.title.trim()).toEqual(otherFile.pageTitle);
});
it('renders a spinner if the file is loading', () => { it('renders a spinner if the file is loading', () => {
const f = file(); const f = file();
f.loading = true; f.loading = true;
...@@ -71,27 +61,30 @@ describe('RepoFile', () => { ...@@ -71,27 +61,30 @@ describe('RepoFile', () => {
expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${vm.file.level * 16}px`); expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${vm.file.level * 16}px`);
}); });
it('does not render commit message and datetime if mini', () => { it('does not render commit message and datetime if mini', (done) => {
RepoStore.openedFiles.push(file());
const vm = createComponent({ const vm = createComponent({
file: file(), file: file(),
}); });
vm.$store.state.openFiles.push(vm.file);
expect(vm.$el.querySelector('.commit-message')).toBeFalsy(); vm.$nextTick(() => {
expect(vm.$el.querySelector('.commit-update')).toBeFalsy(); expect(vm.$el.querySelector('.commit-message')).toBeFalsy();
expect(vm.$el.querySelector('.commit-update')).toBeFalsy();
done();
});
}); });
it('fires linkClicked when the link is clicked', () => { it('fires clickedTreeRow when the link is clicked', () => {
const vm = createComponent({ const vm = createComponent({
file: file(), file: file(),
}); });
spyOn(vm, 'linkClicked'); spyOn(vm, 'clickedTreeRow');
vm.$el.click(); vm.$el.click();
expect(vm.linkClicked).toHaveBeenCalledWith(vm.file); expect(vm.clickedTreeRow).toHaveBeenCalledWith(vm.file);
}); });
describe('submodule', () => { describe('submodule', () => {
...@@ -119,20 +112,4 @@ describe('RepoFile', () => { ...@@ -119,20 +112,4 @@ describe('RepoFile', () => {
expect(vm.$el.querySelector('td').textContent.replace(/\s+/g, ' ')).toContain('submodule name @ 12345678'); expect(vm.$el.querySelector('td').textContent.replace(/\s+/g, ' ')).toContain('submodule name @ 12345678');
}); });
}); });
describe('methods', () => {
describe('linkClicked', () => {
it('$emits fileNameClicked with file obj', () => {
spyOn(eventHub, '$emit');
const vm = createComponent({
file: file(),
});
vm.linkClicked(vm.file);
expect(eventHub.$emit).toHaveBeenCalledWith('fileNameClicked', vm.file);
});
});
});
}); });
import Vue from 'vue'; import Vue from 'vue';
import RepoStore from '~/repo/stores/repo_store'; import store from '~/repo/stores';
import repoLoadingFile from '~/repo/components/repo_loading_file.vue'; import repoLoadingFile from '~/repo/components/repo_loading_file.vue';
import { resetStore } from '../helpers';
describe('RepoLoadingFile', () => { describe('RepoLoadingFile', () => {
function createComponent(propsData) { let vm;
function createComponent() {
const RepoLoadingFile = Vue.extend(repoLoadingFile); const RepoLoadingFile = Vue.extend(repoLoadingFile);
return new RepoLoadingFile({ return new RepoLoadingFile({
propsData, store,
}).$mount(); }).$mount();
} }
...@@ -30,33 +33,30 @@ describe('RepoLoadingFile', () => { ...@@ -30,33 +33,30 @@ describe('RepoLoadingFile', () => {
} }
afterEach(() => { afterEach(() => {
RepoStore.openedFiles = []; vm.$destroy();
resetStore(vm.$store);
}); });
it('renders 3 columns of animated LoC', () => { it('renders 3 columns of animated LoC', () => {
const vm = createComponent({ vm = createComponent();
loading: {
tree: true,
},
hasFiles: false,
});
const columns = [...vm.$el.querySelectorAll('td')]; const columns = [...vm.$el.querySelectorAll('td')];
expect(columns.length).toEqual(3); expect(columns.length).toEqual(3);
assertColumns(columns); assertColumns(columns);
}); });
it('renders 1 column of animated LoC if isMini', () => { it('renders 1 column of animated LoC if isMini', (done) => {
RepoStore.openedFiles = new Array(1); vm = createComponent();
const vm = createComponent({ vm.$store.state.openFiles.push('test');
loading: {
tree: true,
},
hasFiles: false,
});
const columns = [...vm.$el.querySelectorAll('td')];
expect(columns.length).toEqual(1); vm.$nextTick(() => {
assertColumns(columns); const columns = [...vm.$el.querySelectorAll('td')];
expect(columns.length).toEqual(1);
assertColumns(columns);
done();
});
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import repoPrevDirectory from '~/repo/components/repo_prev_directory.vue'; import repoPrevDirectory from '~/repo/components/repo_prev_directory.vue';
import eventHub from '~/repo/event_hub'; import { resetStore } from '../helpers';
describe('RepoPrevDirectory', () => { describe('RepoPrevDirectory', () => {
function createComponent(propsData) { let vm;
const parentLink = 'parent';
function createComponent() {
const RepoPrevDirectory = Vue.extend(repoPrevDirectory); const RepoPrevDirectory = Vue.extend(repoPrevDirectory);
return new RepoPrevDirectory({ const comp = new RepoPrevDirectory({
propsData, store,
}).$mount();
}
it('renders a prev dir link', () => {
const prevUrl = 'prevUrl';
const vm = createComponent({
prevUrl,
}); });
const link = vm.$el.querySelector('a');
spyOn(vm, 'linkClicked'); comp.$store.state.parentTreeUrl = parentLink;
expect(link.href).toMatch(`/${prevUrl}`); return comp.$mount();
expect(link.textContent).toEqual('...'); }
beforeEach(() => {
vm = createComponent();
});
link.click(); afterEach(() => {
vm.$destroy();
expect(vm.linkClicked).toHaveBeenCalledWith(prevUrl); resetStore(vm.$store);
}); });
describe('methods', () => { it('renders a prev dir link', () => {
describe('linkClicked', () => { const link = vm.$el.querySelector('a');
it('$emits linkclicked with prevUrl', () => {
const prevUrl = 'prevUrl';
const vm = createComponent({
prevUrl,
});
spyOn(eventHub, '$emit'); expect(link.href).toMatch(`/${parentLink}`);
expect(link.textContent).toEqual('...');
});
vm.linkClicked(prevUrl); it('clicking row triggers getTreeData', () => {
spyOn(vm, 'getTreeData');
expect(eventHub.$emit).toHaveBeenCalledWith('goToPreviousDirectoryClicked', prevUrl); vm.$el.querySelector('td').click();
});
}); expect(vm.getTreeData).toHaveBeenCalledWith({ endpoint: parentLink });
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import repoPreview from '~/repo/components/repo_preview.vue'; import repoPreview from '~/repo/components/repo_preview.vue';
import RepoStore from '~/repo/stores/repo_store'; import { file, resetStore } from '../helpers';
describe('RepoPreview', () => { describe('RepoPreview', () => {
let vm;
function createComponent() { function createComponent() {
const f = file();
const RepoPreview = Vue.extend(repoPreview); const RepoPreview = Vue.extend(repoPreview);
return new RepoPreview().$mount(); const comp = new RepoPreview({
store,
});
f.active = true;
f.html = 'test';
comp.$store.state.openFiles.push(f);
return comp.$mount();
} }
it('renders a div with the activeFile html', () => { afterEach(() => {
const activeFile = { vm.$destroy();
html: '<p class="file-content">html</p>',
}; resetStore(vm.$store);
RepoStore.activeFile = activeFile; });
const vm = createComponent(); it('renders a div with the activeFile html', () => {
vm = createComponent();
expect(vm.$el.tagName).toEqual('DIV'); expect(vm.$el.tagName).toEqual('DIV');
expect(vm.$el.innerHTML).toContain(activeFile.html); expect(vm.$el.innerHTML).toContain('test');
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import Helper from '~/repo/helpers/repo_helper'; import store from '~/repo/stores';
import RepoService from '~/repo/services/repo_service';
import RepoStore from '~/repo/stores/repo_store';
import repoSidebar from '~/repo/components/repo_sidebar.vue'; import repoSidebar from '~/repo/components/repo_sidebar.vue';
import { file } from '../mock_data'; import { file, resetStore } from '../helpers';
describe('RepoSidebar', () => { describe('RepoSidebar', () => {
let vm; let vm;
function createComponent() { beforeEach(() => {
const RepoSidebar = Vue.extend(repoSidebar); const RepoSidebar = Vue.extend(repoSidebar);
return new RepoSidebar().$mount(); vm = new RepoSidebar({
} store,
});
vm.$store.state.isRoot = true;
vm.$store.state.tree.push(file());
vm.$mount();
});
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
RepoStore.files = []; resetStore(vm.$store);
RepoStore.openedFiles = [];
}); });
it('renders a sidebar', () => { it('renders a sidebar', () => {
RepoStore.files = [file()];
RepoStore.openedFiles = [];
RepoStore.isRoot = true;
vm = createComponent();
const thead = vm.$el.querySelector('thead'); const thead = vm.$el.querySelector('thead');
const tbody = vm.$el.querySelector('tbody'); const tbody = vm.$el.querySelector('tbody');
...@@ -41,139 +40,36 @@ describe('RepoSidebar', () => { ...@@ -41,139 +40,36 @@ describe('RepoSidebar', () => {
expect(tbody.querySelector('.file')).toBeTruthy(); expect(tbody.querySelector('.file')).toBeTruthy();
}); });
it('does not render a thead, renders repo-file-options and sets sidebar-mini class if isMini', () => { it('does not render a thead, renders repo-file-options and sets sidebar-mini class if isMini', (done) => {
RepoStore.openedFiles = [{ vm.$store.state.openFiles.push(vm.$store.state.tree[0]);
id: 0,
}];
vm = createComponent();
expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy();
expect(vm.$el.querySelector('thead')).toBeTruthy();
expect(vm.$el.querySelector('thead .repo-file-options')).toBeTruthy();
});
it('renders 5 loading files if tree is loading and not hasFiles', () => {
RepoStore.loading.tree = true;
RepoStore.files = [];
vm = createComponent();
expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5); Vue.nextTick(() => {
}); expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy();
expect(vm.$el.querySelector('thead')).toBeTruthy();
it('renders a prev directory if is not root', () => { expect(vm.$el.querySelector('thead .repo-file-options')).toBeTruthy();
RepoStore.files = [file()];
RepoStore.isRoot = false;
RepoStore.loading.tree = false;
vm = createComponent();
expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
});
describe('flattendFiles', () => { done();
it('returns a flattend array of files', () => {
const f = file();
f.files.push(file('testing 123'));
const files = [f, file()];
vm = createComponent();
vm.files = files;
expect(vm.flattendFiles.length).toBe(3);
expect(vm.flattendFiles[1].name).toBe('testing 123');
}); });
}); });
describe('methods', () => { it('renders 5 loading files if tree is loading', (done) => {
describe('fileClicked', () => { vm.$store.state.tree = [];
it('should fetch data for new file', () => { vm.$store.state.loading = true;
spyOn(Helper, 'getContent').and.callThrough();
RepoStore.files = [file()];
RepoStore.isRoot = true;
vm = createComponent();
vm.fileClicked(RepoStore.files[0]);
expect(Helper.getContent).toHaveBeenCalledWith(RepoStore.files[0]);
});
it('should not fetch data for already opened files', () => {
const f = file();
spyOn(Helper, 'getFileFromPath').and.returnValue(f);
spyOn(RepoStore, 'setActiveFiles');
vm = createComponent();
vm.fileClicked(f);
expect(RepoStore.setActiveFiles).toHaveBeenCalledWith(f); Vue.nextTick(() => {
}); expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5);
it('should hide files in directory if already open', () => { done();
spyOn(Helper, 'setDirectoryToClosed').and.callThrough();
const f = file();
f.opened = true;
f.type = 'tree';
RepoStore.files = [f];
vm = createComponent();
vm.fileClicked(RepoStore.files[0]);
expect(Helper.setDirectoryToClosed).toHaveBeenCalledWith(RepoStore.files[0]);
});
describe('submodule', () => {
it('opens submodule project URL', () => {
spyOn(gl.utils, 'visitUrl');
const f = file();
f.type = 'submodule';
vm = createComponent();
vm.fileClicked(f);
expect(gl.utils.visitUrl).toHaveBeenCalledWith('url');
});
});
});
describe('goToPreviousDirectoryClicked', () => {
it('should hide files in directory if already open', () => {
const prevUrl = 'foo/bar';
vm = createComponent();
vm.goToPreviousDirectoryClicked(prevUrl);
expect(RepoService.url).toEqual(prevUrl);
});
}); });
});
describe('back button', () => { it('renders a prev directory if is not root', (done) => {
beforeEach(() => { vm.$store.state.isRoot = false;
const f = file();
const file2 = Object.assign({}, file());
file2.url = 'test';
RepoStore.files = [f, file2];
RepoStore.openedFiles = [];
RepoStore.isRoot = true;
vm = createComponent();
});
it('render previous file when using back button', () => {
spyOn(Helper, 'getContent').and.callThrough();
vm.fileClicked(RepoStore.files[1]);
expect(Helper.getContent).toHaveBeenCalledWith(RepoStore.files[1]);
history.pushState({
key: Math.random(),
}, '', RepoStore.files[1].url);
const popEvent = document.createEvent('Event');
popEvent.initEvent('popstate', true, true);
window.dispatchEvent(popEvent);
expect(Helper.getContent.calls.mostRecent().args[0].url).toContain(RepoStore.files[1].url); Vue.nextTick(() => {
expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
window.history.pushState({}, null, '/'); done();
});
}); });
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import repo from '~/repo/components/repo.vue'; import repo from '~/repo/components/repo.vue';
import RepoStore from '~/repo/stores/repo_store'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import Service from '~/repo/services/repo_service'; import { file, resetStore } from '../helpers';
import eventHub from '~/repo/event_hub';
import createComponent from '../../helpers/vue_mount_component_helper';
describe('repo component', () => { describe('repo component', () => {
let vm; let vm;
...@@ -11,86 +10,26 @@ describe('repo component', () => { ...@@ -11,86 +10,26 @@ describe('repo component', () => {
beforeEach(() => { beforeEach(() => {
const Component = Vue.extend(repo); const Component = Vue.extend(repo);
RepoStore.currentBranch = 'master'; vm = createComponentWithStore(Component, store).$mount();
vm = createComponent(Component);
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
RepoStore.currentBranch = ''; resetStore(vm.$store);
}); });
describe('createNewBranch', () => { it('does not render panel right when no files open', () => {
beforeEach(() => { expect(vm.$el.querySelector('.panel-right')).toBeNull();
spyOn(history, 'pushState'); });
});
describe('success', () => {
beforeEach(() => {
spyOn(Service, 'createBranch').and.returnValue(Promise.resolve({
data: {
name: 'test',
},
}));
});
it('calls createBranch with branchName', () => {
eventHub.$emit('createNewBranch', 'test');
expect(Service.createBranch).toHaveBeenCalledWith({
branch: 'test',
ref: RepoStore.currentBranch,
});
});
it('pushes new history state', (done) => {
RepoStore.currentBranch = 'master';
spyOn(vm, 'getCurrentLocation').and.returnValue('http://test.com/master');
eventHub.$emit('createNewBranch', 'test');
setTimeout(() => {
expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'http://test.com/test');
done();
});
});
it('updates stores currentBranch', (done) => {
eventHub.$emit('createNewBranch', 'test');
setTimeout(() => {
expect(RepoStore.currentBranch).toBe('test');
done();
});
});
});
describe('failure', () => {
beforeEach(() => {
spyOn(Service, 'createBranch').and.returnValue(Promise.reject({
response: {
data: {
message: 'test',
},
},
}));
});
it('emits createNewBranchError event', (done) => {
spyOn(eventHub, '$emit').and.callThrough();
eventHub.$emit('createNewBranch', 'test'); it('renders panel right when files are open', (done) => {
vm.$store.state.tree.push(file());
setTimeout(() => { Vue.nextTick(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('createNewBranchError', 'test'); expect(vm.$el.querySelector('.panel-right')).toBeNull();
done(); done();
});
});
}); });
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import store from '~/repo/stores';
import repoTab from '~/repo/components/repo_tab.vue'; import repoTab from '~/repo/components/repo_tab.vue';
import RepoStore from '~/repo/stores/repo_store'; import { file, resetStore } from '../helpers';
describe('RepoTab', () => { describe('RepoTab', () => {
function createComponent(propsData) { function createComponent(propsData) {
const RepoTab = Vue.extend(repoTab); const RepoTab = Vue.extend(repoTab);
return new RepoTab({ return new RepoTab({
store,
propsData, propsData,
}).$mount(); }).$mount();
} }
afterEach(() => {
resetStore(vm.$store);
});
it('renders a close link and a name link', () => { it('renders a close link and a name link', () => {
const tab = {
url: 'url',
name: 'name',
};
const vm = createComponent({ const vm = createComponent({
tab, tab: file(),
}); });
vm.$store.state.openFiles.push(vm.tab);
const close = vm.$el.querySelector('.close-btn'); const close = vm.$el.querySelector('.close-btn');
const name = vm.$el.querySelector(`a[title="${tab.url}"]`); const name = vm.$el.querySelector(`a[title="${vm.tab.url}"]`);
spyOn(vm, 'closeTab');
spyOn(vm, 'tabClicked');
expect(close.querySelector('.fa-times')).toBeTruthy(); expect(close.querySelector('.fa-times')).toBeTruthy();
expect(name.textContent.trim()).toEqual(tab.name); expect(name.textContent.trim()).toEqual(vm.tab.name);
});
it('calls setFileActive when clicking tab', () => {
const vm = createComponent({
tab: file(),
});
spyOn(vm, 'setFileActive');
vm.$el.click();
expect(vm.setFileActive).toHaveBeenCalledWith(vm.tab);
});
it('calls closeFile when clicking close button', () => {
const vm = createComponent({
tab: file(),
});
spyOn(vm, 'closeFile');
close.click(); vm.$el.querySelector('.close-btn').click();
name.click();
expect(vm.closeTab).toHaveBeenCalledWith(tab); expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab });
expect(vm.tabClicked).toHaveBeenCalledWith(tab);
}); });
it('renders an fa-circle icon if tab is changed', () => { it('renders an fa-circle icon if tab is changed', () => {
const tab = { const tab = file();
url: 'url', tab.changed = true;
name: 'name',
changed: true,
};
const vm = createComponent({ const vm = createComponent({
tab, tab,
}); });
...@@ -50,38 +65,41 @@ describe('RepoTab', () => { ...@@ -50,38 +65,41 @@ describe('RepoTab', () => {
describe('methods', () => { describe('methods', () => {
describe('closeTab', () => { describe('closeTab', () => {
it('returns undefined and does not $emit if file is changed', () => { it('does not close tab if is changed', (done) => {
const tab = { const tab = file();
url: 'url', tab.changed = true;
name: 'name', tab.opened = true;
changed: true,
};
const vm = createComponent({ const vm = createComponent({
tab, tab,
}); });
vm.$store.state.openFiles.push(tab);
spyOn(RepoStore, 'removeFromOpenedFiles'); vm.$store.dispatch('setFileActive', tab);
vm.$el.querySelector('.close-btn').click(); vm.$el.querySelector('.close-btn').click();
expect(RepoStore.removeFromOpenedFiles).not.toHaveBeenCalled(); vm.$nextTick(() => {
expect(tab.opened).toBeTruthy();
done();
});
}); });
it('$emits tabclosed event with file obj', () => { it('closes tab when clicking close btn', (done) => {
const tab = { const tab = file('lose');
url: 'url', tab.opened = true;
name: 'name',
changed: false,
};
const vm = createComponent({ const vm = createComponent({
tab, tab,
}); });
vm.$store.state.openFiles.push(tab);
spyOn(RepoStore, 'removeFromOpenedFiles'); vm.$store.dispatch('setFileActive', tab);
vm.$el.querySelector('.close-btn').click(); vm.$el.querySelector('.close-btn').click();
expect(RepoStore.removeFromOpenedFiles).toHaveBeenCalledWith(tab); vm.$nextTick(() => {
expect(tab.opened).toBeFalsy();
done();
});
}); });
}); });
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import RepoStore from '~/repo/stores/repo_store'; import store from '~/repo/stores';
import repoTabs from '~/repo/components/repo_tabs.vue'; import repoTabs from '~/repo/components/repo_tabs.vue';
import { file, resetStore } from '../helpers';
describe('RepoTabs', () => { describe('RepoTabs', () => {
const openedFiles = [{ const openedFiles = [file(), file()];
id: 0,
active: true,
}, {
id: 1,
}];
function createComponent() { function createComponent() {
const RepoTabs = Vue.extend(repoTabs); const RepoTabs = Vue.extend(repoTabs);
return new RepoTabs().$mount(); return new RepoTabs({
store,
}).$mount();
} }
afterEach(() => { afterEach(() => {
RepoStore.openedFiles = []; resetStore(vm.$store);
}); });
it('renders a list of tabs', () => { it('renders a list of tabs', (done) => {
RepoStore.openedFiles = openedFiles;
const vm = createComponent(); const vm = createComponent();
const tabs = [...vm.$el.querySelectorAll(':scope > li')]; openedFiles[0].active = true;
vm.$store.state.openFiles = openedFiles;
vm.$nextTick(() => {
const tabs = [...vm.$el.querySelectorAll(':scope > li')];
expect(tabs.length).toEqual(3);
expect(tabs[0].classList.contains('active')).toBeTruthy();
expect(tabs[1].classList.contains('active')).toBeFalsy();
expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy();
expect(vm.$el.id).toEqual('tabs'); done();
expect(tabs.length).toEqual(3); });
expect(tabs[0].classList.contains('active')).toBeTruthy();
expect(tabs[1].classList.contains('active')).toBeFalsy();
expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy();
}); });
}); });
import RepoHelper from '~/repo/helpers/repo_helper'; import { decorateData } from '~/repo/stores/utils';
import state from '~/repo/stores/state';
// eslint-disable-next-line import/prefer-default-export export const resetStore = (store) => {
export const file = (name = 'name', id = name) => RepoHelper.serializeRepoEntity('blob', { store.replaceState(state());
};
export const file = (name = 'name', id = name, type = '') => decorateData({
id, id,
type,
icon: 'icon', icon: 'icon',
url: 'url', url: 'url',
name, name,
path: name,
last_commit: { last_commit: {
id: '123', id: '123',
message: 'test', message: 'test',
......
import axios from 'axios';
import RepoService from '~/repo/services/repo_service';
import RepoStore from '~/repo/stores/repo_store';
import Api from '~/api';
describe('RepoService', () => {
it('has default json format param', () => {
expect(RepoService.options.params.format).toBe('json');
});
describe('buildParams', () => {
let newParams;
const url = 'url';
beforeEach(() => {
newParams = {};
spyOn(Object, 'assign').and.returnValue(newParams);
});
it('clones params', () => {
const params = RepoService.buildParams(url);
expect(Object.assign).toHaveBeenCalledWith({}, RepoService.options.params);
expect(params).toBe(newParams);
});
it('sets and returns viewer params to richif urlIsRichBlob is true', () => {
spyOn(RepoService, 'urlIsRichBlob').and.returnValue(true);
const params = RepoService.buildParams(url);
expect(params.viewer).toEqual('rich');
});
it('returns params urlIsRichBlob is false', () => {
spyOn(RepoService, 'urlIsRichBlob').and.returnValue(false);
const params = RepoService.buildParams(url);
expect(params.viewer).toBeUndefined();
});
it('calls urlIsRichBlob with the objects url prop if no url arg is provided', () => {
spyOn(RepoService, 'urlIsRichBlob');
RepoService.url = url;
RepoService.buildParams();
expect(RepoService.urlIsRichBlob).toHaveBeenCalledWith(url);
});
});
describe('urlIsRichBlob', () => {
it('returns true for md extension', () => {
const isRichBlob = RepoService.urlIsRichBlob('url.md');
expect(isRichBlob).toBeTruthy();
});
it('returns false for js extension', () => {
const isRichBlob = RepoService.urlIsRichBlob('url.js');
expect(isRichBlob).toBeFalsy();
});
});
describe('getContent', () => {
const params = {};
const url = 'url';
const requestPromise = Promise.resolve();
beforeEach(() => {
spyOn(RepoService, 'buildParams').and.returnValue(params);
spyOn(axios, 'get').and.returnValue(requestPromise);
});
it('calls buildParams and axios.get', () => {
const request = RepoService.getContent(url);
expect(RepoService.buildParams).toHaveBeenCalledWith(url);
expect(axios.get).toHaveBeenCalledWith(url, {
params,
});
expect(request).toBe(requestPromise);
});
it('uses object url prop if no url arg is provided', () => {
RepoService.url = url;
RepoService.getContent();
expect(axios.get).toHaveBeenCalledWith(url, {
params,
});
});
});
describe('getBase64Content', () => {
const url = 'url';
const response = { data: 'data' };
beforeEach(() => {
spyOn(RepoService, 'bufferToBase64');
spyOn(axios, 'get').and.returnValue(Promise.resolve(response));
});
it('calls axios.get and bufferToBase64 on completion', (done) => {
const request = RepoService.getBase64Content(url);
expect(axios.get).toHaveBeenCalledWith(url, {
responseType: 'arraybuffer',
});
expect(request).toEqual(jasmine.any(Promise));
request.then(() => {
expect(RepoService.bufferToBase64).toHaveBeenCalledWith(response.data);
done();
}).catch(done.fail);
});
});
describe('commitFiles', () => {
it('calls commitMultiple and .then commitFlash', (done) => {
const projectId = 'projectId';
const payload = {};
RepoStore.projectId = projectId;
spyOn(Api, 'commitMultiple').and.returnValue(Promise.resolve());
spyOn(RepoService, 'commitFlash');
const apiPromise = RepoService.commitFiles(payload);
expect(Api.commitMultiple).toHaveBeenCalledWith(projectId, payload);
apiPromise.then(() => {
expect(RepoService.commitFlash).toHaveBeenCalled();
done();
}).catch(done.fail);
});
});
describe('commitFlash', () => {
it('calls Flash with data.message', () => {
const data = {
message: 'message',
};
spyOn(window, 'Flash');
RepoService.commitFlash(data);
expect(window.Flash).toHaveBeenCalledWith(data.message);
});
it('calls Flash with success string if short_id and stats', () => {
const data = {
short_id: 'short_id',
stats: {
additions: '4',
deletions: '5',
},
};
spyOn(window, 'Flash');
RepoService.commitFlash(data);
expect(window.Flash).toHaveBeenCalledWith(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
});
});
});
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