Merge branch 'ee1979-gl-modal-vuex' into 'master'
Create shared gl-modal-vuex component and module See merge request gitlab-org/gitlab-ce!24140
This commit is contained in:
commit
833276cd2a
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