Commit fdc71976 authored by Paul Slaughter's avatar Paul Slaughter

Handle file change rename in IDE editor module

- This introduces a payload to the files changed
eventHub so that listeners can react to
*specific* actions.
parent 43abddbd
...@@ -5,7 +5,7 @@ import { visitUrl } from '~/lib/utils/url_utility'; ...@@ -5,7 +5,7 @@ import { visitUrl } from '~/lib/utils/url_utility';
import { deprecatedCreateFlash as flash } from '~/flash'; import { deprecatedCreateFlash as flash } from '~/flash';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { decorateFiles } from '../lib/files'; import { decorateFiles } from '../lib/files';
import { stageKeys } from '../constants'; import { stageKeys, commitActionTypes } from '../constants';
import service from '../services'; import service from '../services';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
...@@ -242,7 +242,7 @@ export const renameEntry = ({ dispatch, commit, state, getters }, { path, name, ...@@ -242,7 +242,7 @@ export const renameEntry = ({ dispatch, commit, state, getters }, { path, name,
} }
} }
dispatch('triggerFilesChange'); dispatch('triggerFilesChange', { type: commitActionTypes.move, path, newPath });
}; };
export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) => export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) =>
......
...@@ -269,7 +269,7 @@ export const removePendingTab = ({ commit }, file) => { ...@@ -269,7 +269,7 @@ export const removePendingTab = ({ commit }, file) => {
eventHub.$emit(`editor.update.model.dispose.${file.key}`); eventHub.$emit(`editor.update.model.dispose.${file.key}`);
}; };
export const triggerFilesChange = () => { export const triggerFilesChange = (ctx, payload = {}) => {
// Used in EE for file mirroring // Used in EE for file mirroring
eventHub.$emit('ide.files.change'); eventHub.$emit('ide.files.change', payload);
}; };
...@@ -13,3 +13,7 @@ export const updateFileEditor = ({ commit }, payload) => { ...@@ -13,3 +13,7 @@ export const updateFileEditor = ({ commit }, payload) => {
export const removeFileEditor = ({ commit }, path) => { export const removeFileEditor = ({ commit }, path) => {
commit(types.REMOVE_FILE_EDITOR, path); commit(types.REMOVE_FILE_EDITOR, path);
}; };
export const renameFileEditor = ({ commit }, payload) => {
commit(types.RENAME_FILE_EDITOR, payload);
};
export const UPDATE_FILE_EDITOR = 'UPDATE_FILE_EDITOR'; export const UPDATE_FILE_EDITOR = 'UPDATE_FILE_EDITOR';
export const REMOVE_FILE_EDITOR = 'REMOVE_FILE_EDITOR'; export const REMOVE_FILE_EDITOR = 'REMOVE_FILE_EDITOR';
export const RENAME_FILE_EDITOR = 'RENAME_FILE_EDITOR';
...@@ -11,4 +11,15 @@ export default { ...@@ -11,4 +11,15 @@ export default {
[types.REMOVE_FILE_EDITOR](state, path) { [types.REMOVE_FILE_EDITOR](state, path) {
Vue.delete(state.fileEditors, path); Vue.delete(state.fileEditors, path);
}, },
[types.RENAME_FILE_EDITOR](state, { path, newPath }) {
const existing = state.fileEditors[path];
// Gracefully do nothing if fileEditor isn't found.
if (!existing) {
return;
}
Vue.delete(state.fileEditors, path);
Vue.set(state.fileEditors, newPath, existing);
},
}; };
import eventHub from '~/ide/eventhub'; import eventHub from '~/ide/eventhub';
import { commitActionTypes } from '~/ide/constants';
const removeUnusedFileEditors = store => { const removeUnusedFileEditors = store => {
Object.keys(store.state.editor.fileEditors) Object.keys(store.state.editor.fileEditors)
...@@ -7,7 +8,12 @@ const removeUnusedFileEditors = store => { ...@@ -7,7 +8,12 @@ const removeUnusedFileEditors = store => {
}; };
export const setupFileEditorsSync = store => { export const setupFileEditorsSync = store => {
eventHub.$on('ide.files.change', () => { eventHub.$on('ide.files.change', ({ type, ...payload } = {}) => {
if (type === commitActionTypes.move) {
store.dispatch('editor/renameFileEditor', payload);
} else {
// The files have changed, but the specific change is not known.
removeUnusedFileEditors(store); removeUnusedFileEditors(store);
}
}); });
}; };
...@@ -46,6 +46,20 @@ RSpec.describe 'IDE user sees editor info', :js do ...@@ -46,6 +46,20 @@ RSpec.describe 'IDE user sees editor info', :js do
end end
end end
it 'persists position after rename' do
ide_open_file('README.md')
ide_set_editor_position(4, 10)
ide_open_file('files/js/application.js')
ide_rename_file('README.md', 'READING_RAINBOW.md')
ide_open_file('READING_RAINBOW.md')
within find('.ide-status-bar') do
expect(page).to have_content('4:10')
end
end
it 'persists position' do it 'persists position' do
ide_open_file('README.md') ide_open_file('README.md')
ide_set_editor_position(4, 10) ide_set_editor_position(4, 10)
......
import * as pathUtils from 'path'; import * as pathUtils from 'path';
import { decorateData } from '~/ide/stores/utils'; import { decorateData } from '~/ide/stores/utils';
import { commitActionTypes } from '~/ide/constants';
export const file = (name = 'name', id = name, type = '', parent = null) => export const file = (name = 'name', id = name, type = '', parent = null) =>
decorateData({ decorateData({
...@@ -28,3 +29,17 @@ export const createEntriesFromPaths = paths => ...@@ -28,3 +29,17 @@ export const createEntriesFromPaths = paths =>
...entries, ...entries,
}; };
}, {}); }, {});
export const createTriggerChangeAction = payload => ({
type: 'triggerFilesChange',
...(payload ? { payload } : {}),
});
export const createTriggerRenamePayload = (path, newPath) => ({
type: commitActionTypes.move,
path,
newPath,
});
export const createTriggerRenameAction = (path, newPath) =>
createTriggerChangeAction(createTriggerRenamePayload(path, newPath));
...@@ -7,7 +7,7 @@ import * as types from '~/ide/stores/mutation_types'; ...@@ -7,7 +7,7 @@ import * as types from '~/ide/stores/mutation_types';
import service from '~/ide/services'; import service from '~/ide/services';
import { createRouter } from '~/ide/ide_router'; import { createRouter } from '~/ide/ide_router';
import eventHub from '~/ide/eventhub'; import eventHub from '~/ide/eventhub';
import { file } from '../../helpers'; import { file, createTriggerRenameAction } from '../../helpers';
const ORIGINAL_CONTENT = 'original content'; const ORIGINAL_CONTENT = 'original content';
const RELATIVE_URL_ROOT = '/gitlab'; const RELATIVE_URL_ROOT = '/gitlab';
...@@ -785,13 +785,19 @@ describe('IDE store file actions', () => { ...@@ -785,13 +785,19 @@ describe('IDE store file actions', () => {
}); });
describe('triggerFilesChange', () => { describe('triggerFilesChange', () => {
const { payload: renamePayload } = createTriggerRenameAction('test', '123');
beforeEach(() => { beforeEach(() => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
}); });
it('emits event that files have changed', () => { it.each`
return store.dispatch('triggerFilesChange').then(() => { args | payload
expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change'); ${[]} | ${{}}
${[renamePayload]} | ${renamePayload}
`('emits event that files have changed (args=$args)', ({ args, payload }) => {
return store.dispatch('triggerFilesChange', ...args).then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change', payload);
}); });
}); });
}); });
......
...@@ -19,7 +19,7 @@ import { ...@@ -19,7 +19,7 @@ import {
} from '~/ide/stores/actions'; } from '~/ide/stores/actions';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import * as types from '~/ide/stores/mutation_types'; import * as types from '~/ide/stores/mutation_types';
import { file } from '../helpers'; import { file, createTriggerRenameAction, createTriggerChangeAction } from '../helpers';
import testAction from '../../helpers/vuex_action_helper'; import testAction from '../../helpers/vuex_action_helper';
import eventHub from '~/ide/eventhub'; import eventHub from '~/ide/eventhub';
...@@ -522,7 +522,7 @@ describe('Multi-file store actions', () => { ...@@ -522,7 +522,7 @@ describe('Multi-file store actions', () => {
'path', 'path',
store.state, store.state,
[{ type: types.DELETE_ENTRY, payload: 'path' }], [{ type: types.DELETE_ENTRY, payload: 'path' }],
[{ type: 'stageChange', payload: 'path' }, { type: 'triggerFilesChange' }], [{ type: 'stageChange', payload: 'path' }, createTriggerChangeAction()],
done, done,
); );
}); });
...@@ -551,7 +551,7 @@ describe('Multi-file store actions', () => { ...@@ -551,7 +551,7 @@ describe('Multi-file store actions', () => {
[{ type: types.DELETE_ENTRY, payload: 'testFolder/entry-to-delete' }], [{ type: types.DELETE_ENTRY, payload: 'testFolder/entry-to-delete' }],
[ [
{ type: 'stageChange', payload: 'testFolder/entry-to-delete' }, { type: 'stageChange', payload: 'testFolder/entry-to-delete' },
{ type: 'triggerFilesChange' }, createTriggerChangeAction(),
], ],
done, done,
); );
...@@ -614,7 +614,7 @@ describe('Multi-file store actions', () => { ...@@ -614,7 +614,7 @@ describe('Multi-file store actions', () => {
testEntry.path, testEntry.path,
store.state, store.state,
[{ type: types.DELETE_ENTRY, payload: testEntry.path }], [{ type: types.DELETE_ENTRY, payload: testEntry.path }],
[{ type: 'stageChange', payload: testEntry.path }, { type: 'triggerFilesChange' }], [{ type: 'stageChange', payload: testEntry.path }, createTriggerChangeAction()],
done, done,
); );
}); });
...@@ -754,7 +754,7 @@ describe('Multi-file store actions', () => { ...@@ -754,7 +754,7 @@ describe('Multi-file store actions', () => {
payload: origEntry, payload: origEntry,
}, },
], ],
[{ type: 'triggerFilesChange' }], [createTriggerRenameAction('renamed', 'orig')],
done, done,
); );
}); });
...@@ -767,7 +767,7 @@ describe('Multi-file store actions', () => { ...@@ -767,7 +767,7 @@ describe('Multi-file store actions', () => {
{ path: 'orig', name: 'renamed' }, { path: 'orig', name: 'renamed' },
store.state, store.state,
[expect.objectContaining({ type: types.RENAME_ENTRY })], [expect.objectContaining({ type: types.RENAME_ENTRY })],
[{ type: 'triggerFilesChange' }], [createTriggerRenameAction('orig', 'renamed')],
done, done,
); );
}); });
......
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import * as types from '~/ide/stores/modules/editor/mutation_types'; import * as types from '~/ide/stores/modules/editor/mutation_types';
import * as actions from '~/ide/stores/modules/editor/actions'; import * as actions from '~/ide/stores/modules/editor/actions';
import { createTriggerRenamePayload } from '../../../helpers';
describe('~/ide/stores/modules/editor/actions', () => { describe('~/ide/stores/modules/editor/actions', () => {
describe('updateFileEditor', () => { describe('updateFileEditor', () => {
...@@ -22,4 +23,14 @@ describe('~/ide/stores/modules/editor/actions', () => { ...@@ -22,4 +23,14 @@ describe('~/ide/stores/modules/editor/actions', () => {
]); ]);
}); });
}); });
describe('renameFileEditor', () => {
it('commits with payload', () => {
const payload = createTriggerRenamePayload('test', 'test123');
testAction(actions.renameFileEditor, payload, {}, [
{ type: types.RENAME_FILE_EDITOR, payload },
]);
});
});
}); });
import { createDefaultFileEditor } from '~/ide/stores/modules/editor/utils'; import { createDefaultFileEditor } from '~/ide/stores/modules/editor/utils';
import * as types from '~/ide/stores/modules/editor/mutation_types'; import * as types from '~/ide/stores/modules/editor/mutation_types';
import mutations from '~/ide/stores/modules/editor/mutations'; import mutations from '~/ide/stores/modules/editor/mutations';
import { createTriggerRenamePayload } from '../../../helpers';
const TEST_PATH = 'test/path.md'; const TEST_PATH = 'test/path.md';
...@@ -60,4 +61,18 @@ describe('~/ide/stores/modules/editor/mutations', () => { ...@@ -60,4 +61,18 @@ describe('~/ide/stores/modules/editor/mutations', () => {
expect(state).toEqual({ fileEditors: expected }); expect(state).toEqual({ fileEditors: expected });
}); });
}); });
describe(types.RENAME_FILE_EDITOR, () => {
it.each`
fileEditors | payload | expected
${{ foo: {} }} | ${createTriggerRenamePayload('does/not/exist', 'abc')} | ${{ foo: {} }}
${{ foo: { a: 1 }, bar: {} }} | ${createTriggerRenamePayload('foo', 'abc/def')} | ${{ 'abc/def': { a: 1 }, bar: {} }}
`('renames fileEditor at $payload', ({ fileEditors, payload, expected }) => {
const state = { fileEditors };
mutations[types.RENAME_FILE_EDITOR](state, payload);
expect(state).toEqual({ fileEditors: expected });
});
});
}); });
...@@ -2,10 +2,13 @@ import Vuex from 'vuex'; ...@@ -2,10 +2,13 @@ import Vuex from 'vuex';
import eventHub from '~/ide/eventhub'; import eventHub from '~/ide/eventhub';
import { createStoreOptions } from '~/ide/stores'; import { createStoreOptions } from '~/ide/stores';
import { setupFileEditorsSync } from '~/ide/stores/modules/editor/setup'; import { setupFileEditorsSync } from '~/ide/stores/modules/editor/setup';
import { createTriggerRenamePayload } from '../../../helpers';
describe('~/ide/stores/modules/editor/setup', () => { describe('~/ide/stores/modules/editor/setup', () => {
it('when files change is emitted, removes unused fileEditors', async () => { let store;
const store = new Vuex.Store(createStoreOptions());
beforeEach(() => {
store = new Vuex.Store(createStoreOptions());
store.state.entries = { store.state.entries = {
foo: {}, foo: {},
bar: {}, bar: {},
...@@ -16,6 +19,9 @@ describe('~/ide/stores/modules/editor/setup', () => { ...@@ -16,6 +19,9 @@ describe('~/ide/stores/modules/editor/setup', () => {
}; };
setupFileEditorsSync(store); setupFileEditorsSync(store);
});
it('when files change is emitted, removes unused fileEditors', () => {
eventHub.$emit('ide.files.change'); eventHub.$emit('ide.files.change');
expect(store.state.entries).toEqual({ expect(store.state.entries).toEqual({
...@@ -26,4 +32,13 @@ describe('~/ide/stores/modules/editor/setup', () => { ...@@ -26,4 +32,13 @@ describe('~/ide/stores/modules/editor/setup', () => {
foo: {}, foo: {},
}); });
}); });
it('when files rename is emitted, renames fileEditor', () => {
eventHub.$emit('ide.files.change', createTriggerRenamePayload('foo', 'foo_new'));
expect(store.state.editor.fileEditors).toEqual({
foo_new: {},
bizz: {},
});
});
}); });
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