diff options
Diffstat (limited to 'spec/frontend/ide/stores/actions/file_spec.js')
-rw-r--r-- | spec/frontend/ide/stores/actions/file_spec.js | 805 |
1 files changed, 805 insertions, 0 deletions
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js new file mode 100644 index 00000000000..283ea266821 --- /dev/null +++ b/spec/frontend/ide/stores/actions/file_spec.js @@ -0,0 +1,805 @@ +import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import store from '~/ide/stores'; +import * as actions from '~/ide/stores/actions/file'; +import * as types from '~/ide/stores/mutation_types'; +import service from '~/ide/services'; +import router from '~/ide/ide_router'; +import eventHub from '~/ide/eventhub'; +import { file, resetStore } from '../../helpers'; +import testAction from '../../../helpers/vuex_action_helper'; + +const RELATIVE_URL_ROOT = '/gitlab'; + +describe('IDE store file actions', () => { + let mock; + let originalGon; + + beforeEach(() => { + mock = new MockAdapter(axios); + originalGon = window.gon; + window.gon = { + ...window.gon, + relative_url_root: RELATIVE_URL_ROOT, + }; + + jest.spyOn(router, 'push').mockImplementation(() => {}); + }); + + afterEach(() => { + mock.restore(); + resetStore(store); + window.gon = originalGon; + }); + + describe('closeFile', () => { + let localFile; + + beforeEach(() => { + localFile = file('testFile'); + localFile.active = true; + localFile.opened = true; + localFile.parentTreeUrl = 'parentTreeUrl'; + + store.state.openFiles.push(localFile); + store.state.entries[localFile.path] = localFile; + }); + + it('closes open files', done => { + store + .dispatch('closeFile', localFile) + .then(() => { + expect(localFile.opened).toBeFalsy(); + expect(localFile.active).toBeFalsy(); + expect(store.state.openFiles.length).toBe(0); + + done(); + }) + .catch(done.fail); + }); + + it('closes file even if file has changes', done => { + store.state.changedFiles.push(localFile); + + store + .dispatch('closeFile', localFile) + .then(Vue.nextTick) + .then(() => { + expect(store.state.openFiles.length).toBe(0); + expect(store.state.changedFiles.length).toBe(1); + + done(); + }) + .catch(done.fail); + }); + + it('closes file & opens next available file', done => { + const f = { + ...file('newOpenFile'), + url: '/newOpenFile', + }; + + store.state.openFiles.push(f); + store.state.entries[f.path] = f; + + store + .dispatch('closeFile', localFile) + .then(Vue.nextTick) + .then(() => { + expect(router.push).toHaveBeenCalledWith(`/project${f.url}`); + + done(); + }) + .catch(done.fail); + }); + + it('removes file if it pending', done => { + store.state.openFiles.push({ + ...localFile, + pending: true, + }); + + store + .dispatch('closeFile', localFile) + .then(() => { + expect(store.state.openFiles.length).toBe(0); + + done(); + }) + .catch(done.fail); + }); + }); + + describe('setFileActive', () => { + let localFile; + let scrollToTabSpy; + let oldScrollToTab; + + beforeEach(() => { + scrollToTabSpy = jest.fn(); + oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line + store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line + + localFile = file('setThisActive'); + + store.state.entries[localFile.path] = localFile; + }); + + afterEach(() => { + store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line + }); + + it('calls scrollToTab', () => { + const dispatch = jest.fn(); + + actions.setFileActive( + { commit() {}, state: store.state, getters: store.getters, dispatch }, + localFile.path, + ); + + expect(dispatch).toHaveBeenCalledWith('scrollToTab'); + }); + + it('commits SET_FILE_ACTIVE', () => { + const commit = jest.fn(); + + actions.setFileActive( + { commit, state: store.state, getters: store.getters, dispatch() {} }, + localFile.path, + ); + + expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', { + path: localFile.path, + active: true, + }); + }); + + it('sets current active file to not active', () => { + const f = file('newActive'); + store.state.entries[f.path] = f; + localFile.active = true; + store.state.openFiles.push(localFile); + + const commit = jest.fn(); + + actions.setFileActive( + { commit, state: store.state, getters: store.getters, dispatch() {} }, + f.path, + ); + + expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', { + path: localFile.path, + active: false, + }); + }); + }); + + describe('getFileData', () => { + let localFile; + + beforeEach(() => { + jest.spyOn(service, 'getFileData'); + + localFile = file(`newCreate-${Math.random()}`); + store.state.entries[localFile.path] = localFile; + + store.state.currentProjectId = 'test/test'; + store.state.currentBranchId = 'master'; + + store.state.projects['test/test'] = { + branches: { + master: { + commit: { + id: '7297abc', + }, + }, + }, + }; + }); + + describe('success', () => { + beforeEach(() => { + mock.onGet(`${RELATIVE_URL_ROOT}/test/test/7297abc/${localFile.path}`).replyOnce( + 200, + { + blame_path: 'blame_path', + commits_path: 'commits_path', + permalink: 'permalink', + raw_path: 'raw_path', + binary: false, + html: '123', + render_error: '', + }, + { + 'page-title': 'testing getFileData', + }, + ); + }); + + it('calls the service', done => { + store + .dispatch('getFileData', { path: localFile.path }) + .then(() => { + expect(service.getFileData).toHaveBeenCalledWith( + `${RELATIVE_URL_ROOT}/test/test/7297abc/${localFile.path}`, + ); + + done(); + }) + .catch(done.fail); + }); + + it('sets the file data', done => { + store + .dispatch('getFileData', { path: localFile.path }) + .then(() => { + expect(localFile.blamePath).toBe('blame_path'); + + done(); + }) + .catch(done.fail); + }); + + it('sets document title with the branchId', done => { + store + .dispatch('getFileData', { path: localFile.path }) + .then(() => { + expect(document.title).toBe(`${localFile.path} · master · test/test · GitLab`); + done(); + }) + .catch(done.fail); + }); + + it('sets the file as active', done => { + store + .dispatch('getFileData', { path: localFile.path }) + .then(() => { + expect(localFile.active).toBeTruthy(); + + done(); + }) + .catch(done.fail); + }); + + it('sets the file not as active if we pass makeFileActive false', done => { + store + .dispatch('getFileData', { path: localFile.path, makeFileActive: false }) + .then(() => { + expect(localFile.active).toBeFalsy(); + + done(); + }) + .catch(done.fail); + }); + + it('adds the file to open files', done => { + store + .dispatch('getFileData', { path: localFile.path }) + .then(() => { + expect(store.state.openFiles.length).toBe(1); + expect(store.state.openFiles[0].name).toBe(localFile.name); + + done(); + }) + .catch(done.fail); + }); + }); + + describe('Re-named success', () => { + beforeEach(() => { + localFile = file(`newCreate-${Math.random()}`); + localFile.url = `project/getFileDataURL`; + localFile.prevPath = 'old-dull-file'; + localFile.path = 'new-shiny-file'; + store.state.entries[localFile.path] = localFile; + + mock.onGet(`${RELATIVE_URL_ROOT}/test/test/7297abc/old-dull-file`).replyOnce( + 200, + { + blame_path: 'blame_path', + commits_path: 'commits_path', + permalink: 'permalink', + raw_path: 'raw_path', + binary: false, + html: '123', + render_error: '', + }, + { + 'page-title': 'testing old-dull-file', + }, + ); + }); + + it('sets document title considering `prevPath` on a file', done => { + store + .dispatch('getFileData', { path: localFile.path }) + .then(() => { + expect(document.title).toBe(`new-shiny-file · master · test/test · GitLab`); + + done(); + }) + .catch(done.fail); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${RELATIVE_URL_ROOT}/test/test/7297abc/${localFile.path}`).networkError(); + }); + + it('dispatches error action', done => { + const dispatch = jest.fn(); + + actions + .getFileData( + { state: store.state, commit() {}, dispatch, getters: store.getters }, + { path: localFile.path }, + ) + .then(() => { + expect(dispatch).toHaveBeenCalledWith('setErrorMessage', { + text: 'An error occurred whilst loading the file.', + action: expect.any(Function), + actionText: 'Please try again', + actionPayload: { + path: localFile.path, + makeFileActive: true, + }, + }); + + done(); + }) + .catch(done.fail); + }); + }); + }); + + describe('getRawFileData', () => { + let tmpFile; + + beforeEach(() => { + jest.spyOn(service, 'getRawFileData'); + + tmpFile = file('tmpFile'); + store.state.entries[tmpFile.path] = tmpFile; + }); + + describe('success', () => { + beforeEach(() => { + mock.onGet(/(.*)/).replyOnce(200, 'raw'); + }); + + it('calls getRawFileData service method', done => { + store + .dispatch('getRawFileData', { path: tmpFile.path }) + .then(() => { + expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile); + + done(); + }) + .catch(done.fail); + }); + + it('updates file raw data', done => { + store + .dispatch('getRawFileData', { path: tmpFile.path }) + .then(() => { + expect(tmpFile.raw).toBe('raw'); + + done(); + }) + .catch(done.fail); + }); + + it('calls also getBaseRawFileData service method', done => { + jest.spyOn(service, 'getBaseRawFileData').mockReturnValue(Promise.resolve('baseraw')); + + store.state.currentProjectId = 'gitlab-org/gitlab-ce'; + store.state.currentMergeRequestId = '1'; + store.state.projects = { + 'gitlab-org/gitlab-ce': { + mergeRequests: { + 1: { + baseCommitSha: 'SHA', + }, + }, + }, + }; + + tmpFile.mrChange = { new_file: false }; + + store + .dispatch('getRawFileData', { path: tmpFile.path }) + .then(() => { + expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA'); + expect(tmpFile.baseRaw).toBe('baseraw'); + + done(); + }) + .catch(done.fail); + }); + }); + + describe('return JSON', () => { + beforeEach(() => { + mock.onGet(/(.*)/).replyOnce(200, JSON.stringify({ test: '123' })); + }); + + it('does not parse returned JSON', done => { + store + .dispatch('getRawFileData', { path: tmpFile.path }) + .then(() => { + expect(tmpFile.raw).toEqual('{"test":"123"}'); + + done(); + }) + .catch(done.fail); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(/(.*)/).networkError(); + }); + + it('dispatches error action', done => { + const dispatch = jest.fn(); + + actions + .getRawFileData({ state: store.state, commit() {}, dispatch }, { path: tmpFile.path }) + .then(done.fail) + .catch(() => { + expect(dispatch).toHaveBeenCalledWith('setErrorMessage', { + text: 'An error occurred whilst loading the file content.', + action: expect.any(Function), + actionText: 'Please try again', + actionPayload: { + path: tmpFile.path, + }, + }); + + done(); + }); + }); + }); + }); + + describe('changeFileContent', () => { + let tmpFile; + + beforeEach(() => { + tmpFile = file('tmpFile'); + tmpFile.content = '\n'; + tmpFile.raw = '\n'; + store.state.entries[tmpFile.path] = tmpFile; + }); + + it('updates file content', done => { + store + .dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content\n', + }) + .then(() => { + expect(tmpFile.content).toBe('content\n'); + + done(); + }) + .catch(done.fail); + }); + + it('adds a newline to the end of the file if it doesnt already exist', done => { + store + .dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content', + }) + .then(() => { + expect(tmpFile.content).toBe('content\n'); + + done(); + }) + .catch(done.fail); + }); + + it('adds file into changedFiles array', done => { + store + .dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content', + }) + .then(() => { + expect(store.state.changedFiles.length).toBe(1); + + done(); + }) + .catch(done.fail); + }); + + it('adds file once into changedFiles array', done => { + store + .dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content', + }) + .then(() => + store.dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content 123', + }), + ) + .then(() => { + expect(store.state.changedFiles.length).toBe(1); + + done(); + }) + .catch(done.fail); + }); + + it('removes file from changedFiles array if not changed', done => { + store + .dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content\n', + }) + .then(() => + store.dispatch('changeFileContent', { + path: tmpFile.path, + content: '\n', + }), + ) + .then(() => { + expect(store.state.changedFiles.length).toBe(0); + + done(); + }) + .catch(done.fail); + }); + + it('bursts unused seal', done => { + store + .dispatch('changeFileContent', { + path: tmpFile.path, + content: 'content', + }) + .then(() => { + expect(store.state.unusedSeal).toBe(false); + + done(); + }) + .catch(done.fail); + }); + }); + + describe('discardFileChanges', () => { + let tmpFile; + + beforeEach(() => { + jest.spyOn(eventHub, '$on').mockImplementation(() => {}); + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + + tmpFile = file(); + tmpFile.content = 'testing'; + + store.state.changedFiles.push(tmpFile); + store.state.entries[tmpFile.path] = tmpFile; + }); + + it('resets file content', done => { + store + .dispatch('discardFileChanges', tmpFile.path) + .then(() => { + expect(tmpFile.content).not.toBe('testing'); + + done(); + }) + .catch(done.fail); + }); + + it('removes file from changedFiles array', done => { + store + .dispatch('discardFileChanges', tmpFile.path) + .then(() => { + expect(store.state.changedFiles.length).toBe(0); + + done(); + }) + .catch(done.fail); + }); + + it('closes temp file', done => { + tmpFile.tempFile = true; + tmpFile.opened = true; + + store + .dispatch('discardFileChanges', tmpFile.path) + .then(() => { + expect(tmpFile.opened).toBeFalsy(); + + done(); + }) + .catch(done.fail); + }); + + it('does not re-open a closed temp file', done => { + tmpFile.tempFile = true; + + expect(tmpFile.opened).toBeFalsy(); + + store + .dispatch('discardFileChanges', tmpFile.path) + .then(() => { + expect(tmpFile.opened).toBeFalsy(); + + done(); + }) + .catch(done.fail); + }); + + it('pushes route for active file', done => { + tmpFile.active = true; + store.state.openFiles.push(tmpFile); + + store + .dispatch('discardFileChanges', tmpFile.path) + .then(() => { + expect(router.push).toHaveBeenCalledWith(`/project${tmpFile.url}`); + + done(); + }) + .catch(done.fail); + }); + + it('emits eventHub event to dispose cached model', done => { + store + .dispatch('discardFileChanges', tmpFile.path) + .then(() => { + expect(eventHub.$emit).toHaveBeenCalled(); + + done(); + }) + .catch(done.fail); + }); + }); + + describe('stageChange', () => { + it('calls STAGE_CHANGE with file path', done => { + testAction( + actions.stageChange, + 'path', + store.state, + [ + { type: types.STAGE_CHANGE, payload: 'path' }, + { type: types.SET_LAST_COMMIT_MSG, payload: '' }, + ], + [], + done, + ); + }); + }); + + describe('unstageChange', () => { + it('calls UNSTAGE_CHANGE with file path', done => { + testAction( + actions.unstageChange, + 'path', + store.state, + [{ type: types.UNSTAGE_CHANGE, payload: 'path' }], + [], + done, + ); + }); + }); + + describe('openPendingTab', () => { + let f; + + beforeEach(() => { + f = { + ...file(), + projectId: '123', + }; + + store.state.entries[f.path] = f; + }); + + it('makes file pending in openFiles', done => { + store + .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }) + .then(() => { + expect(store.state.openFiles[0].pending).toBe(true); + }) + .then(done) + .catch(done.fail); + }); + + it('returns true when opened', done => { + store + .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }) + .then(added => { + expect(added).toBe(true); + }) + .then(done) + .catch(done.fail); + }); + + it('returns false when already opened', done => { + store.state.openFiles.push({ + ...f, + active: true, + key: `pending-${f.key}`, + }); + + store + .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }) + .then(added => { + expect(added).toBe(false); + }) + .then(done) + .catch(done.fail); + }); + + it('pushes router URL when added', done => { + store.state.currentBranchId = 'master'; + + store + .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }) + .then(() => { + expect(router.push).toHaveBeenCalledWith('/project/123/tree/master/'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('removePendingTab', () => { + let f; + + beforeEach(() => { + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + + f = { + ...file('pendingFile'), + pending: true, + }; + }); + + it('removes pending file from open files', done => { + store.state.openFiles.push(f); + + store + .dispatch('removePendingTab', f) + .then(() => { + expect(store.state.openFiles.length).toBe(0); + }) + .then(done) + .catch(done.fail); + }); + + it('emits event to dispose model', done => { + store + .dispatch('removePendingTab', f) + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.${f.key}`); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('triggerFilesChange', () => { + beforeEach(() => { + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + }); + + it('emits event that files have changed', done => { + store + .dispatch('triggerFilesChange') + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change'); + }) + .then(done) + .catch(done.fail); + }); + }); +}); |