Create shared gl-modal-vuex component and module
**Why?** It is significantly easier to manage the visibility of the modal in Vuex. The module contains the state and mutations to manage this. The component wraps GlModal and syncs the visibility with the module.
This commit is contained in:
parent
c50b0e58fe
commit
708df374f5
9 changed files with 353 additions and 0 deletions
|
@ -0,0 +1,69 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
|
||||
/**
|
||||
* This component keeps the GlModal's visibility in sync with the given vuex module.
|
||||
*/
|
||||
export default {
|
||||
components: {
|
||||
GlModal,
|
||||
},
|
||||
props: {
|
||||
modalId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
modalModule: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isVisible(state) {
|
||||
return state[this.modalModule].isVisible;
|
||||
},
|
||||
}),
|
||||
attrs() {
|
||||
const { modalId, modalModule, ...attrs } = this.$attrs;
|
||||
|
||||
return attrs;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isVisible(val) {
|
||||
return val ? this.bsShow() : this.bsHide();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
syncShow(dispatch) {
|
||||
return dispatch(`${this.modalModule}/show`);
|
||||
},
|
||||
syncHide(dispatch) {
|
||||
return dispatch(`${this.modalModule}/hide`);
|
||||
},
|
||||
}),
|
||||
bsShow() {
|
||||
this.$root.$emit('bv::show::modal', this.modalId);
|
||||
},
|
||||
bsHide() {
|
||||
// $root.$emit is a workaround because other b-modal approaches don't work yet with gl-modal
|
||||
this.$root.$emit('bv::hide::modal', this.modalId);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-modal
|
||||
v-bind="attrs"
|
||||
:modal-id="modalId"
|
||||
v-on="$listeners"
|
||||
@shown="syncShow"
|
||||
@hidden="syncHide"
|
||||
>
|
||||
<slot></slot>
|
||||
</gl-modal>
|
||||
</template>
|
17
app/assets/javascripts/vuex_shared/modules/modal/actions.js
Normal file
17
app/assets/javascripts/vuex_shared/modules/modal/actions.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import * as types from './mutation_types';
|
||||
|
||||
export const open = ({ commit }, data) => {
|
||||
commit(types.OPEN, data);
|
||||
};
|
||||
|
||||
export const close = ({ commit }) => {
|
||||
commit(types.CLOSE);
|
||||
};
|
||||
|
||||
export const show = ({ commit }) => {
|
||||
commit(types.SHOW);
|
||||
};
|
||||
|
||||
export const hide = ({ commit }) => {
|
||||
commit(types.HIDE);
|
||||
};
|
10
app/assets/javascripts/vuex_shared/modules/modal/index.js
Normal file
10
app/assets/javascripts/vuex_shared/modules/modal/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import state from './state';
|
||||
import mutations from './mutations';
|
||||
import * as actions from './actions';
|
||||
|
||||
export default () => ({
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
mutations,
|
||||
actions,
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
export const HIDE = 'HIDE';
|
||||
export const SHOW = 'SHOW';
|
||||
export const OPEN = 'OPEN';
|
||||
export const CLOSE = 'CLOSE';
|
|
@ -0,0 +1,18 @@
|
|||
import * as types from './mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SHOW](state) {
|
||||
state.isVisible = true;
|
||||
},
|
||||
[types.HIDE](state) {
|
||||
state.isVisible = false;
|
||||
},
|
||||
[types.OPEN](state, data) {
|
||||
state.data = data;
|
||||
state.isVisible = true;
|
||||
},
|
||||
[types.CLOSE](state) {
|
||||
state.data = null;
|
||||
state.isVisible = false;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
export default () => ({
|
||||
isVisible: false,
|
||||
data: null,
|
||||
});
|
151
spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js
Normal file
151
spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import GlModalVuex from '~/vue_shared/components/gl_modal_vuex.vue';
|
||||
import createState from '~/vuex_shared/modules/modal/state';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
const TEST_SLOT = 'Lorem ipsum modal dolar sit.';
|
||||
const TEST_MODAL_ID = 'my-modal-id';
|
||||
const TEST_MODULE = 'myModal';
|
||||
|
||||
describe('GlModalVuex', () => {
|
||||
let wrapper;
|
||||
let state;
|
||||
let actions;
|
||||
|
||||
const factory = (options = {}) => {
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
[TEST_MODULE]: {
|
||||
namespaced: true,
|
||||
state,
|
||||
actions,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const propsData = {
|
||||
modalId: TEST_MODAL_ID,
|
||||
modalModule: TEST_MODULE,
|
||||
...options.propsData,
|
||||
};
|
||||
|
||||
wrapper = shallowMount(localVue.extend(GlModalVuex), {
|
||||
...options,
|
||||
localVue,
|
||||
store,
|
||||
propsData,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
state = createState();
|
||||
|
||||
actions = {
|
||||
show: jasmine.createSpy('show'),
|
||||
hide: jasmine.createSpy('hide'),
|
||||
};
|
||||
});
|
||||
|
||||
it('renders gl-modal', () => {
|
||||
factory({
|
||||
slots: {
|
||||
default: `<div>${TEST_SLOT}</div>`,
|
||||
},
|
||||
});
|
||||
const glModal = wrapper.find(GlModal);
|
||||
|
||||
expect(glModal.props('modalId')).toBe(TEST_MODAL_ID);
|
||||
expect(glModal.text()).toContain(TEST_SLOT);
|
||||
});
|
||||
|
||||
it('passes props through to gl-modal', () => {
|
||||
const title = 'Test Title';
|
||||
const okVariant = 'success';
|
||||
|
||||
factory({
|
||||
propsData: {
|
||||
title,
|
||||
okTitle: title,
|
||||
okVariant,
|
||||
},
|
||||
});
|
||||
const glModal = wrapper.find(GlModal);
|
||||
|
||||
expect(glModal.attributes('title')).toEqual(title);
|
||||
expect(glModal.attributes('oktitle')).toEqual(title);
|
||||
expect(glModal.attributes('okvariant')).toEqual(okVariant);
|
||||
});
|
||||
|
||||
it('passes listeners through to gl-modal', () => {
|
||||
const ok = jasmine.createSpy('ok');
|
||||
|
||||
factory({
|
||||
listeners: { ok },
|
||||
});
|
||||
|
||||
const glModal = wrapper.find(GlModal);
|
||||
glModal.vm.$emit('ok');
|
||||
|
||||
expect(ok).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls vuex action on show', () => {
|
||||
expect(actions.show).not.toHaveBeenCalled();
|
||||
|
||||
factory();
|
||||
|
||||
const glModal = wrapper.find(GlModal);
|
||||
glModal.vm.$emit('shown');
|
||||
|
||||
expect(actions.show).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls vuex action on hide', () => {
|
||||
expect(actions.hide).not.toHaveBeenCalled();
|
||||
|
||||
factory();
|
||||
|
||||
const glModal = wrapper.find(GlModal);
|
||||
glModal.vm.$emit('hidden');
|
||||
|
||||
expect(actions.hide).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls bootstrap show when isVisible changes', done => {
|
||||
state.isVisible = false;
|
||||
|
||||
factory();
|
||||
const rootEmit = spyOn(wrapper.vm.$root, '$emit');
|
||||
|
||||
state.isVisible = true;
|
||||
|
||||
localVue
|
||||
.nextTick()
|
||||
.then(() => {
|
||||
expect(rootEmit).toHaveBeenCalledWith('bv::show::modal', TEST_MODAL_ID);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('calls bootstrap hide when isVisible changes', done => {
|
||||
state.isVisible = true;
|
||||
|
||||
factory();
|
||||
const rootEmit = spyOn(wrapper.vm.$root, '$emit');
|
||||
|
||||
state.isVisible = false;
|
||||
|
||||
localVue
|
||||
.nextTick()
|
||||
.then(() => {
|
||||
expect(rootEmit).toHaveBeenCalledWith('bv::hide::modal', TEST_MODAL_ID);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
31
spec/javascripts/vuex_shared/modules/modal/actions_spec.js
Normal file
31
spec/javascripts/vuex_shared/modules/modal/actions_spec.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import * as types from '~/vuex_shared/modules/modal/mutation_types';
|
||||
import * as actions from '~/vuex_shared/modules/modal/actions';
|
||||
import testAction from 'spec/helpers/vuex_action_helper';
|
||||
|
||||
describe('Vuex ModalModule actions', () => {
|
||||
describe('open', () => {
|
||||
it('works', done => {
|
||||
const data = { id: 7 };
|
||||
|
||||
testAction(actions.open, data, {}, [{ type: types.OPEN, payload: data }], [], done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('close', () => {
|
||||
it('works', done => {
|
||||
testAction(actions.close, null, {}, [{ type: types.CLOSE }], [], done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('show', () => {
|
||||
it('works', done => {
|
||||
testAction(actions.show, null, {}, [{ type: types.SHOW }], [], done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hide', () => {
|
||||
it('works', done => {
|
||||
testAction(actions.hide, null, {}, [{ type: types.HIDE }], [], done);
|
||||
});
|
||||
});
|
||||
});
|
49
spec/javascripts/vuex_shared/modules/modal/mutations_spec.js
Normal file
49
spec/javascripts/vuex_shared/modules/modal/mutations_spec.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import mutations from '~/vuex_shared/modules/modal/mutations';
|
||||
import * as types from '~/vuex_shared/modules/modal/mutation_types';
|
||||
|
||||
describe('Vuex ModalModule mutations', () => {
|
||||
describe(types.SHOW, () => {
|
||||
it('sets isVisible to true', () => {
|
||||
const state = {
|
||||
isVisible: false,
|
||||
};
|
||||
|
||||
mutations[types.SHOW](state);
|
||||
|
||||
expect(state).toEqual({
|
||||
isVisible: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(types.HIDE, () => {
|
||||
it('sets isVisible to false', () => {
|
||||
const state = {
|
||||
isVisible: true,
|
||||
};
|
||||
|
||||
mutations[types.HIDE](state);
|
||||
|
||||
expect(state).toEqual({
|
||||
isVisible: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(types.OPEN, () => {
|
||||
it('sets data and sets isVisible to true', () => {
|
||||
const data = { id: 7 };
|
||||
const state = {
|
||||
isVisible: false,
|
||||
data: null,
|
||||
};
|
||||
|
||||
mutations[types.OPEN](state, data);
|
||||
|
||||
expect(state).toEqual({
|
||||
isVisible: true,
|
||||
data,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue