Commit 437a3edc authored by Himanshu Kapoor's avatar Himanshu Kapoor Committed by Kushal Pandya

Fix regression introduced due to keep-alive

Due to the left sidebar components in the Web IDE being
kept alive, a regression was introduced because kept alive
components don't trigger the `mounted` life cycle hook. So move
the logic to `activated` hook, while also keeping it in the `mounted`
hook (for the first render).
parent aec045de
...@@ -23,26 +23,32 @@ export default { ...@@ -23,26 +23,32 @@ export default {
}, },
}, },
mounted() { mounted() {
if (this.activeFile && this.activeFile.pending && !this.activeFile.deleted) { this.initialize();
this.$router.push(this.getUrlForPath(this.activeFile.path), () => { },
this.updateViewer('editor'); activated() {
}); this.initialize();
} else if (this.activeFile && this.activeFile.deleted) {
this.resetOpenFiles();
}
this.$nextTick(() => {
this.updateViewer(this.currentMergeRequestId ? viewerTypes.mr : viewerTypes.diff);
});
}, },
methods: { methods: {
...mapActions(['updateViewer', 'resetOpenFiles']), ...mapActions(['updateViewer', 'resetOpenFiles']),
initialize() {
if (this.activeFile && this.activeFile.pending && !this.activeFile.deleted) {
this.$router.push(this.getUrlForPath(this.activeFile.path), () => {
this.updateViewer(viewerTypes.edit);
});
} else if (this.activeFile && this.activeFile.deleted) {
this.resetOpenFiles();
}
this.$nextTick(() => {
this.updateViewer(this.currentMergeRequestId ? viewerTypes.mr : viewerTypes.diff);
});
},
}, },
}; };
</script> </script>
<template> <template>
<ide-tree-list :viewer-type="viewer" header-class="ide-review-header"> <ide-tree-list header-class="ide-review-header">
<template #header> <template #header>
<div class="ide-review-button-holder"> <div class="ide-review-button-holder">
{{ __('Review') }} {{ __('Review') }}
......
<script> <script>
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import { modalTypes } from '../constants'; import { modalTypes, viewerTypes } from '../constants';
import IdeTreeList from './ide_tree_list.vue'; import IdeTreeList from './ide_tree_list.vue';
import Upload from './new_dropdown/upload.vue'; import Upload from './new_dropdown/upload.vue';
import NewEntryButton from './new_dropdown/button.vue'; import NewEntryButton from './new_dropdown/button.vue';
...@@ -18,15 +18,10 @@ export default { ...@@ -18,15 +18,10 @@ export default {
...mapGetters(['currentProject', 'currentTree', 'activeFile', 'getUrlForPath']), ...mapGetters(['currentProject', 'currentTree', 'activeFile', 'getUrlForPath']),
}, },
mounted() { mounted() {
if (!this.activeFile) return; this.initialize();
},
if (this.activeFile.pending && !this.activeFile.deleted) { activated() {
this.$router.push(this.getUrlForPath(this.activeFile.path), () => { this.initialize();
this.updateViewer('editor');
});
} else if (this.activeFile.deleted) {
this.resetOpenFiles();
}
}, },
methods: { methods: {
...mapActions(['updateViewer', 'createTempEntry', 'resetOpenFiles']), ...mapActions(['updateViewer', 'createTempEntry', 'resetOpenFiles']),
...@@ -36,12 +31,27 @@ export default { ...@@ -36,12 +31,27 @@ export default {
createNewFolder() { createNewFolder() {
this.$refs.newModal.open(modalTypes.tree); this.$refs.newModal.open(modalTypes.tree);
}, },
initialize() {
this.$nextTick(() => {
this.updateViewer(viewerTypes.edit);
});
if (!this.activeFile) return;
if (this.activeFile.pending && !this.activeFile.deleted) {
this.$router.push(this.getUrlForPath(this.activeFile.path), () => {
this.updateViewer(viewerTypes.edit);
});
} else if (this.activeFile.deleted) {
this.resetOpenFiles();
}
},
}, },
}; };
</script> </script>
<template> <template>
<ide-tree-list viewer-type="editor"> <ide-tree-list>
<template #header> <template #header>
{{ __('Edit') }} {{ __('Edit') }}
<div class="ide-tree-actions ml-auto d-flex"> <div class="ide-tree-actions ml-auto d-flex">
......
...@@ -12,10 +12,6 @@ export default { ...@@ -12,10 +12,6 @@ export default {
FileTree, FileTree,
}, },
props: { props: {
viewerType: {
type: String,
required: true,
},
headerClass: { headerClass: {
type: String, type: String,
required: false, required: false,
...@@ -29,11 +25,8 @@ export default { ...@@ -29,11 +25,8 @@ export default {
return !this.currentTree || this.currentTree.loading; return !this.currentTree || this.currentTree.loading;
}, },
}, },
mounted() {
this.updateViewer(this.viewerType);
},
methods: { methods: {
...mapActions(['updateViewer', 'toggleTreeOpen']), ...mapActions(['toggleTreeOpen']),
}, },
IdeFileRow, IdeFileRow,
}; };
......
...@@ -26,28 +26,34 @@ export default { ...@@ -26,28 +26,34 @@ export default {
}, },
}, },
mounted() { mounted() {
const file = this.initialize();
this.lastOpenedFile && this.lastOpenedFile.type !== 'tree' },
? this.lastOpenedFile activated() {
: this.activeFile; this.initialize();
if (!file) return;
this.openPendingTab({
file,
keyPrefix: file.staged ? stageKeys.staged : stageKeys.unstaged,
})
.then(changeViewer => {
if (changeViewer) {
this.updateViewer('diff');
}
})
.catch(e => {
throw e;
});
}, },
methods: { methods: {
...mapActions(['openPendingTab', 'updateViewer', 'updateActivityBarView']), ...mapActions(['openPendingTab', 'updateViewer', 'updateActivityBarView']),
initialize() {
const file =
this.lastOpenedFile && this.lastOpenedFile.type !== 'tree'
? this.lastOpenedFile
: this.activeFile;
if (!file) return;
this.openPendingTab({
file,
keyPrefix: file.staged ? stageKeys.staged : stageKeys.unstaged,
})
.then(changeViewer => {
if (changeViewer) {
this.updateViewer('diff');
}
})
.catch(e => {
throw e;
});
},
}, },
stageKeys, stageKeys,
}; };
......
import Vue from 'vue';
export function keepAlive(KeptAliveComponent) {
return Vue.extend({
components: {
KeptAliveComponent,
},
data() {
return {
view: 'KeptAliveComponent',
};
},
methods: {
async activate() {
this.view = 'KeptAliveComponent';
await this.$nextTick();
},
async deactivate() {
this.view = 'div';
await this.$nextTick();
},
async reactivate() {
await this.deactivate();
await this.activate();
},
},
template: `<keep-alive><component :is="view"></component></keep-alive>`,
});
}
import { mount } from '@vue/test-utils';
import { keepAlive } from './keep_alive_component_helper';
const component = {
template: '<div>Test Component</div>',
};
describe('keepAlive', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(keepAlive(component));
});
afterEach(() => {
wrapper.destroy();
});
it('converts a component to a keep-alive component', async () => {
const { element } = wrapper.find(component);
await wrapper.vm.deactivate();
expect(wrapper.find(component).exists()).toBe(false);
await wrapper.vm.activate();
// assert that when the component is destroyed and re-rendered, the
// newly rendered component has the reference to the old component
// (i.e. the old component was deactivated and activated)
expect(wrapper.find(component).element).toBe(element);
});
});
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import IdeReview from '~/ide/components/ide_review.vue'; import IdeReview from '~/ide/components/ide_review.vue';
import EditorModeDropdown from '~/ide/components/editor_mode_dropdown.vue';
import { createStore } from '~/ide/stores'; import { createStore } from '~/ide/stores';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { trimText } from '../../helpers/text_helper'; import { trimText } from '../../helpers/text_helper';
import { keepAlive } from '../../helpers/keep_alive_component_helper';
import { file } from '../helpers'; import { file } from '../helpers';
import { projectData } from '../mock_data'; import { projectData } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('IDE review mode', () => { describe('IDE review mode', () => {
const Component = Vue.extend(IdeReview); let wrapper;
let vm;
let store; let store;
beforeEach(() => { beforeEach(() => {
...@@ -21,15 +26,53 @@ describe('IDE review mode', () => { ...@@ -21,15 +26,53 @@ describe('IDE review mode', () => {
loading: false, loading: false,
}); });
vm = createComponentWithStore(Component, store).$mount(); wrapper = mount(keepAlive(IdeReview), {
store,
localVue,
});
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
}); });
it('renders list of files', () => { it('renders list of files', () => {
expect(vm.$el.textContent).toContain('fileName'); expect(wrapper.text()).toContain('fileName');
});
describe('activated', () => {
let inititializeSpy;
beforeEach(async () => {
inititializeSpy = jest.spyOn(wrapper.find(IdeReview).vm, 'initialize');
store.state.viewer = 'editor';
await wrapper.vm.reactivate();
});
it('re initializes the component', () => {
expect(inititializeSpy).toHaveBeenCalled();
});
it('updates viewer to "diff" by default', () => {
expect(store.state.viewer).toBe('diff');
});
describe('merge request is defined', () => {
beforeEach(async () => {
store.state.currentMergeRequestId = '1';
store.state.projects.abcproject.mergeRequests['1'] = {
iid: 123,
web_url: 'testing123',
};
await wrapper.vm.reactivate();
});
it('updates viewer to "mrdiff"', async () => {
expect(store.state.viewer).toBe('mrdiff');
});
});
}); });
describe('merge request', () => { describe('merge request', () => {
...@@ -40,32 +83,27 @@ describe('IDE review mode', () => { ...@@ -40,32 +83,27 @@ describe('IDE review mode', () => {
web_url: 'testing123', web_url: 'testing123',
}; };
return vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
it('renders edit dropdown', () => { it('renders edit dropdown', () => {
expect(vm.$el.querySelector('.btn')).not.toBe(null); expect(wrapper.find(EditorModeDropdown).exists()).toBe(true);
}); });
it('renders merge request link & IID', () => { it('renders merge request link & IID', async () => {
store.state.viewer = 'mrdiff'; store.state.viewer = 'mrdiff';
return vm.$nextTick(() => { await wrapper.vm.$nextTick();
const link = vm.$el.querySelector('.ide-review-sub-header');
expect(link.querySelector('a').getAttribute('href')).toBe('testing123'); expect(trimText(wrapper.text())).toContain('Merge request (!123)');
expect(trimText(link.textContent)).toBe('Merge request (!123)');
});
}); });
it('changes text to latest changes when viewer is not mrdiff', () => { it('changes text to latest changes when viewer is not mrdiff', async () => {
store.state.viewer = 'diff'; store.state.viewer = 'diff';
return vm.$nextTick(() => { await wrapper.vm.$nextTick();
expect(trimText(vm.$el.querySelector('.ide-review-sub-header').textContent)).toBe(
'Latest changes', expect(wrapper.text()).toContain('Latest changes');
);
});
}); });
}); });
}); });
...@@ -38,15 +38,9 @@ describe('IDE tree list', () => { ...@@ -38,15 +38,9 @@ describe('IDE tree list', () => {
beforeEach(() => { beforeEach(() => {
bootstrapWithTree(); bootstrapWithTree();
jest.spyOn(vm, 'updateViewer');
vm.$mount(); vm.$mount();
}); });
it('updates viewer on mount', () => {
expect(vm.updateViewer).toHaveBeenCalledWith('edit');
});
it('renders loading indicator', done => { it('renders loading indicator', done => {
store.state.trees['abcproject/master'].loading = true; store.state.trees['abcproject/master'].loading = true;
...@@ -67,8 +61,6 @@ describe('IDE tree list', () => { ...@@ -67,8 +61,6 @@ describe('IDE tree list', () => {
beforeEach(() => { beforeEach(() => {
bootstrapWithTree(emptyBranchTree); bootstrapWithTree(emptyBranchTree);
jest.spyOn(vm, 'updateViewer');
vm.$mount(); vm.$mount();
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
import IdeTree from '~/ide/components/ide_tree.vue'; import IdeTree from '~/ide/components/ide_tree.vue';
import { createStore } from '~/ide/stores'; import { createStore } from '~/ide/stores';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; import { keepAlive } from '../../helpers/keep_alive_component_helper';
import { file } from '../helpers'; import { file } from '../helpers';
import { projectData } from '../mock_data'; import { projectData } from '../mock_data';
describe('IdeRepoTree', () => { const localVue = createLocalVue();
localVue.use(Vuex);
describe('IdeTree', () => {
let store; let store;
let vm; let wrapper;
beforeEach(() => { beforeEach(() => {
store = createStore(); store = createStore();
const IdeRepoTree = Vue.extend(IdeTree);
store.state.currentProjectId = 'abcproject'; store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master'; store.state.currentBranchId = 'master';
store.state.projects.abcproject = { ...projectData }; store.state.projects.abcproject = { ...projectData };
...@@ -22,14 +25,36 @@ describe('IdeRepoTree', () => { ...@@ -22,14 +25,36 @@ describe('IdeRepoTree', () => {
loading: false, loading: false,
}); });
vm = createComponentWithStore(IdeRepoTree, store).$mount(); wrapper = mount(keepAlive(IdeTree), {
store,
localVue,
});
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
}); });
it('renders list of files', () => { it('renders list of files', () => {
expect(vm.$el.textContent).toContain('fileName'); expect(wrapper.text()).toContain('fileName');
});
describe('activated', () => {
let inititializeSpy;
beforeEach(async () => {
inititializeSpy = jest.spyOn(wrapper.find(IdeTree).vm, 'initialize');
store.state.viewer = 'diff';
await wrapper.vm.reactivate();
});
it('re initializes the component', () => {
expect(inititializeSpy).toHaveBeenCalled();
});
it('updates viewer to "editor" by default', () => {
expect(store.state.viewer).toBe('editor');
});
}); });
}); });
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { createStore } from '~/ide/stores'; import { createStore } from '~/ide/stores';
import { createRouter } from '~/ide/ide_router'; import { createRouter } from '~/ide/ide_router';
import { keepAlive } from '../../helpers/keep_alive_component_helper';
import RepoCommitSection from '~/ide/components/repo_commit_section.vue'; import RepoCommitSection from '~/ide/components/repo_commit_section.vue';
import EmptyState from '~/ide/components/commit_sidebar/empty_state.vue'; import EmptyState from '~/ide/components/commit_sidebar/empty_state.vue';
import { stageKeys } from '~/ide/constants'; import { stageKeys } from '~/ide/constants';
...@@ -14,7 +15,7 @@ describe('RepoCommitSection', () => { ...@@ -14,7 +15,7 @@ describe('RepoCommitSection', () => {
let store; let store;
function createComponent() { function createComponent() {
wrapper = mount(RepoCommitSection, { store }); wrapper = mount(keepAlive(RepoCommitSection), { store });
} }
function setupDefaultState() { function setupDefaultState() {
...@@ -64,6 +65,7 @@ describe('RepoCommitSection', () => { ...@@ -64,6 +65,7 @@ describe('RepoCommitSection', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('empty state', () => { describe('empty state', () => {
...@@ -168,4 +170,21 @@ describe('RepoCommitSection', () => { ...@@ -168,4 +170,21 @@ describe('RepoCommitSection', () => {
expect(wrapper.find(EmptyState).exists()).toBe(false); expect(wrapper.find(EmptyState).exists()).toBe(false);
}); });
}); });
describe('activated', () => {
let inititializeSpy;
beforeEach(async () => {
createComponent();
inititializeSpy = jest.spyOn(wrapper.find(RepoCommitSection).vm, 'initialize');
store.state.viewer = 'diff';
await wrapper.vm.reactivate();
});
it('re initializes the component', () => {
expect(inititializeSpy).toHaveBeenCalled();
});
});
}); });
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