From 5c483fd8656805b39b451fcd25c1efbd112d3cae Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 27 Jun 2018 10:43:26 +0100 Subject: [PATCH] specs --- app/assets/javascripts/ide/services/index.js | 7 +- .../javascripts/ide/stores/actions/tree.js | 3 +- spec/javascripts/api_spec.js | 25 ++++ .../ide/components/error_message_spec.js | 104 ++++++++++++++++ spec/javascripts/ide/components/ide_spec.js | 14 +++ .../ide/stores/actions/project_spec.js | 43 ++++++- .../ide/stores/actions/tree_spec.js | 115 ++++++++++++------ 7 files changed, 265 insertions(+), 46 deletions(-) create mode 100644 spec/javascripts/ide/components/error_message_spec.js diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js index da9de25302a..5e642067141 100644 --- a/app/assets/javascripts/ide/services/index.js +++ b/app/assets/javascripts/ide/services/index.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import VueResource from 'vue-resource'; +import axios from '~/lib/utils/axios_utils'; import Api from '~/api'; Vue.use(VueResource); @@ -69,11 +70,7 @@ export default { }, getFiles(projectUrl, branchId) { const url = `${projectUrl}/files/${branchId}`; - return Vue.http.get(url, { - params: { - format: 'json', - }, - }); + return axios.get(url, { params: { format: 'json' } }); }, lastCommitPipelines({ getters }) { const commitSha = getters.lastCommit.id; diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js index 48750615f39..f8af23ac70b 100644 --- a/app/assets/javascripts/ide/stores/actions/tree.js +++ b/app/assets/javascripts/ide/stores/actions/tree.js @@ -70,8 +70,7 @@ export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = service .getFiles(selectedProject.web_url, branchId) - .then(res => res.json()) - .then(data => { + .then(({ data }) => { const worker = new FilesDecoratorWorker(); worker.addEventListener('message', e => { const { entries, treeList } = e.data; diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index e8435116221..c53b6da4b48 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -362,4 +362,29 @@ describe('Api', () => { .catch(done.fail); }); }); + + describe('createBranch', () => { + it('creates new branch', done => { + const ref = 'master'; + const branch = 'new-branch-name'; + const dummyProjectPath = 'gitlab-org/gitlab-ce'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent( + dummyProjectPath, + )}/repository/branches`; + + spyOn(axios, 'post').and.callThrough(); + + mock.onPost(expectedUrl).replyOnce(200, { + name: branch, + }); + + Api.createBranch(dummyProjectPath, { ref, branch }) + .then(({ data }) => { + expect(data.name).toBe(branch); + expect(axios.post).toHaveBeenCalledWith(expectedUrl, { ref, branch }); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/ide/components/error_message_spec.js b/spec/javascripts/ide/components/error_message_spec.js new file mode 100644 index 00000000000..70d13a6ceb2 --- /dev/null +++ b/spec/javascripts/ide/components/error_message_spec.js @@ -0,0 +1,104 @@ +import Vue from 'vue'; +import store from '~/ide/stores'; +import ErrorMessage from '~/ide/components/error_message.vue'; +import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { resetStore } from '../helpers'; + +describe('IDE error message component', () => { + const Component = Vue.extend(ErrorMessage); + let vm; + + beforeEach(() => { + vm = createComponentWithStore(Component, store, { + message: { + text: 'error message', + action: null, + actionText: null, + }, + }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + resetStore(vm.$store); + }); + + it('renders error message', () => { + expect(vm.$el.textContent).toContain('error message'); + }); + + it('clears error message on click', () => { + spyOn(vm, 'setErrorMessage'); + + vm.$el.click(); + + expect(vm.setErrorMessage).toHaveBeenCalledWith(null); + }); + + describe('with action', () => { + beforeEach(done => { + vm.message.action = 'testAction'; + vm.message.actionText = 'test action'; + vm.message.actionPayload = 'testActionPayload'; + + spyOn(vm.$store, 'dispatch').and.returnValue(Promise.resolve()); + + vm.$nextTick(done); + }); + + it('renders action button', () => { + expect(vm.$el.querySelector('.flash-action')).not.toBe(null); + expect(vm.$el.textContent).toContain('test action'); + }); + + it('does not clear error message on click', () => { + spyOn(vm, 'setErrorMessage'); + + vm.$el.click(); + + expect(vm.setErrorMessage).not.toHaveBeenCalled(); + }); + + it('dispatches action', done => { + vm.$el.querySelector('.flash-action').click(); + + vm.$nextTick(() => { + expect(vm.$store.dispatch).toHaveBeenCalledWith('testAction', 'testActionPayload'); + + done(); + }); + }); + + it('does not dispatch action when already loading', () => { + vm.isLoading = true; + + vm.$el.querySelector('.flash-action').click(); + + expect(vm.$store.dispatch).not.toHaveBeenCalledWith(); + }); + + it('resets isLoading after click', done => { + vm.$el.querySelector('.flash-action').click(); + + expect(vm.isLoading).toBe(true); + + vm.$nextTick(() => { + expect(vm.isLoading).toBe(false); + + done(); + }); + }); + + it('shows loading icon when isLoading is true', done => { + expect(vm.$el.querySelector('.loading-container').style.display).not.toBe(''); + + vm.isLoading = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.loading-container').style.display).toBe(''); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js index 045a60e56a0..708c9fe69af 100644 --- a/spec/javascripts/ide/components/ide_spec.js +++ b/spec/javascripts/ide/components/ide_spec.js @@ -114,4 +114,18 @@ describe('ide component', () => { expect(vm.mousetrapStopCallback(null, document.querySelector('.inputarea'), 't')).toBe(true); }); }); + + it('shows error message when set', done => { + expect(vm.$el.querySelector('.flash-container')).toBe(null); + + vm.$store.state.errorMessage = { + text: 'error', + }; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.flash-container')).not.toBe(null); + + done(); + }); + }); }); diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index 5529fe44ce5..ab8bca52093 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -1,7 +1,10 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import { refreshLastCommitData, showBranchNotFoundError, createNewBranchFromDefault, + getBranchData, } from '~/ide/stores/actions'; import store from '~/ide/stores'; import projectActions from '~/ide/stores/actions/project'; @@ -11,11 +14,19 @@ import { resetStore } from '../../helpers'; import testAction from '../../../helpers/vuex_action_helper'; describe('IDE store project actions', () => { + let mock; + beforeEach(() => { - store.state.projects['abc/def'] = {}; + mock = new MockAdapter(axios); + + store.state.projects['abc/def'] = { + branches: {}, + }; }); afterEach(() => { + mock.restore(); + resetStore(store); }); @@ -164,4 +175,34 @@ describe('IDE store project actions', () => { }); }); }); + + describe('getBranchData', () => { + describe('error', () => { + it('dispatches branch not found action when response is 404', done => { + const dispatch = jasmine.createSpy('dispatchSpy'); + + mock.onGet(/(.*)/).replyOnce(404); + + getBranchData( + { + commit() {}, + dispatch, + state: store.state, + }, + { + projectId: 'abc/def', + branchId: 'master-testing', + }, + ) + .then(done.fail) + .catch(() => { + expect(dispatch.calls.argsFor(0)).toEqual([ + 'showBranchNotFoundError', + 'master-testing', + ]); + done(); + }); + }); + }); + }); }); diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js index cefed9ddb43..612a439cfbf 100644 --- a/spec/javascripts/ide/stores/actions/tree_spec.js +++ b/spec/javascripts/ide/stores/actions/tree_spec.js @@ -1,7 +1,9 @@ +import MockAdapter from 'axios-mock-adapter'; import Vue from 'vue'; import testAction from 'spec/helpers/vuex_action_helper'; -import { showTreeEntry } from '~/ide/stores/actions/tree'; +import { showTreeEntry, getFiles } from '~/ide/stores/actions/tree'; import * as types from '~/ide/stores/mutation_types'; +import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; import service from '~/ide/services'; import router from '~/ide/ide_router'; @@ -9,6 +11,7 @@ import { file, resetStore, createEntriesFromPaths } from '../../helpers'; describe('Multi-file store tree actions', () => { let projectTree; + let mock; const basicCallParameters = { endpoint: 'rootEndpoint', @@ -20,6 +23,8 @@ describe('Multi-file store tree actions', () => { beforeEach(() => { spyOn(router, 'push'); + mock = new MockAdapter(axios); + store.state.currentProjectId = 'abcproject'; store.state.currentBranchId = 'master'; store.state.projects.abcproject = { @@ -33,49 +38,85 @@ describe('Multi-file store tree actions', () => { }); afterEach(() => { + mock.restore(); resetStore(store); }); describe('getFiles', () => { - beforeEach(() => { - spyOn(service, 'getFiles').and.returnValue( - Promise.resolve({ - json: () => - Promise.resolve([ - 'file.txt', - 'folder/fileinfolder.js', - 'folder/subfolder/fileinsubfolder.js', - ]), - }), - ); + describe('success', () => { + beforeEach(() => { + spyOn(service, 'getFiles').and.callThrough(); + + mock + .onGet(/(.*)/) + .replyOnce(200, [ + 'file.txt', + 'folder/fileinfolder.js', + 'folder/subfolder/fileinsubfolder.js', + ]); + }); + + it('calls service getFiles', done => { + store + .dispatch('getFiles', basicCallParameters) + .then(() => { + expect(service.getFiles).toHaveBeenCalledWith('', 'master'); + + done(); + }) + .catch(done.fail); + }); + + it('adds data into tree', done => { + store + .dispatch('getFiles', basicCallParameters) + .then(() => { + projectTree = store.state.trees['abcproject/master']; + expect(projectTree.tree.length).toBe(2); + expect(projectTree.tree[0].type).toBe('tree'); + expect(projectTree.tree[0].tree[1].name).toBe('fileinfolder.js'); + expect(projectTree.tree[1].type).toBe('blob'); + expect(projectTree.tree[0].tree[0].tree[0].type).toBe('blob'); + expect(projectTree.tree[0].tree[0].tree[0].name).toBe('fileinsubfolder.js'); + + done(); + }) + .catch(done.fail); + }); }); - it('calls service getFiles', done => { - store - .dispatch('getFiles', basicCallParameters) - .then(() => { - expect(service.getFiles).toHaveBeenCalledWith('', 'master'); + describe('error', () => { + it('dispatches branch not found actions when response is 404', done => { + const dispatch = jasmine.createSpy('dispatchSpy'); - done(); - }) - .catch(done.fail); - }); + store.state.projects = { + 'abc/def': { + web_url: `${gl.TEST_HOST}/files`, + }, + }; - it('adds data into tree', done => { - store - .dispatch('getFiles', basicCallParameters) - .then(() => { - projectTree = store.state.trees['abcproject/master']; - expect(projectTree.tree.length).toBe(2); - expect(projectTree.tree[0].type).toBe('tree'); - expect(projectTree.tree[0].tree[1].name).toBe('fileinfolder.js'); - expect(projectTree.tree[1].type).toBe('blob'); - expect(projectTree.tree[0].tree[0].tree[0].type).toBe('blob'); - expect(projectTree.tree[0].tree[0].tree[0].name).toBe('fileinsubfolder.js'); + mock.onGet(/(.*)/).replyOnce(404); - done(); - }) - .catch(done.fail); + getFiles( + { + commit() {}, + dispatch, + state: store.state, + }, + { + projectId: 'abc/def', + branchId: 'master-testing', + }, + ) + .then(done.fail) + .catch(() => { + expect(dispatch.calls.argsFor(0)).toEqual([ + 'showBranchNotFoundError', + 'master-testing', + ]); + done(); + }); + }); }); }); @@ -122,9 +163,7 @@ describe('Multi-file store tree actions', () => { { type: types.SET_TREE_OPEN, payload: 'grandparent/parent' }, { type: types.SET_TREE_OPEN, payload: 'grandparent' }, ], - [ - { type: 'showTreeEntry' }, - ], + [{ type: 'showTreeEntry' }], done, ); });