Skip to content

Commit 833276c

Browse files
committed
Merge branch 'ee1979-gl-modal-vuex' into 'master'
Create shared gl-modal-vuex component and module See merge request gitlab-org/gitlab-ce!24140
2 parents 6d99475 + 708df37 commit 833276c

File tree

9 files changed

+353
-0
lines changed

9 files changed

+353
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<script>
2+
import { mapState, mapActions } from 'vuex';
3+
import { GlModal } from '@gitlab/ui';
4+
5+
/**
6+
* This component keeps the GlModal's visibility in sync with the given vuex module.
7+
*/
8+
export default {
9+
components: {
10+
GlModal,
11+
},
12+
props: {
13+
modalId: {
14+
type: String,
15+
required: true,
16+
},
17+
modalModule: {
18+
type: String,
19+
required: true,
20+
},
21+
},
22+
computed: {
23+
...mapState({
24+
isVisible(state) {
25+
return state[this.modalModule].isVisible;
26+
},
27+
}),
28+
attrs() {
29+
const { modalId, modalModule, ...attrs } = this.$attrs;
30+
31+
return attrs;
32+
},
33+
},
34+
watch: {
35+
isVisible(val) {
36+
return val ? this.bsShow() : this.bsHide();
37+
},
38+
},
39+
methods: {
40+
...mapActions({
41+
syncShow(dispatch) {
42+
return dispatch(`${this.modalModule}/show`);
43+
},
44+
syncHide(dispatch) {
45+
return dispatch(`${this.modalModule}/hide`);
46+
},
47+
}),
48+
bsShow() {
49+
this.$root.$emit('bv::show::modal', this.modalId);
50+
},
51+
bsHide() {
52+
// $root.$emit is a workaround because other b-modal approaches don't work yet with gl-modal
53+
this.$root.$emit('bv::hide::modal', this.modalId);
54+
},
55+
},
56+
};
57+
</script>
58+
59+
<template>
60+
<gl-modal
61+
v-bind="attrs"
62+
:modal-id="modalId"
63+
v-on="$listeners"
64+
@shown="syncShow"
65+
@hidden="syncHide"
66+
>
67+
<slot></slot>
68+
</gl-modal>
69+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as types from './mutation_types';
2+
3+
export const open = ({ commit }, data) => {
4+
commit(types.OPEN, data);
5+
};
6+
7+
export const close = ({ commit }) => {
8+
commit(types.CLOSE);
9+
};
10+
11+
export const show = ({ commit }) => {
12+
commit(types.SHOW);
13+
};
14+
15+
export const hide = ({ commit }) => {
16+
commit(types.HIDE);
17+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import state from './state';
2+
import mutations from './mutations';
3+
import * as actions from './actions';
4+
5+
export default () => ({
6+
namespaced: true,
7+
state: state(),
8+
mutations,
9+
actions,
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const HIDE = 'HIDE';
2+
export const SHOW = 'SHOW';
3+
export const OPEN = 'OPEN';
4+
export const CLOSE = 'CLOSE';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as types from './mutation_types';
2+
3+
export default {
4+
[types.SHOW](state) {
5+
state.isVisible = true;
6+
},
7+
[types.HIDE](state) {
8+
state.isVisible = false;
9+
},
10+
[types.OPEN](state, data) {
11+
state.data = data;
12+
state.isVisible = true;
13+
},
14+
[types.CLOSE](state) {
15+
state.data = null;
16+
state.isVisible = false;
17+
},
18+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default () => ({
2+
isVisible: false,
3+
data: null,
4+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { shallowMount, createLocalVue } from '@vue/test-utils';
2+
import Vuex from 'vuex';
3+
import { GlModal } from '@gitlab/ui';
4+
import GlModalVuex from '~/vue_shared/components/gl_modal_vuex.vue';
5+
import createState from '~/vuex_shared/modules/modal/state';
6+
7+
const localVue = createLocalVue();
8+
localVue.use(Vuex);
9+
10+
const TEST_SLOT = 'Lorem ipsum modal dolar sit.';
11+
const TEST_MODAL_ID = 'my-modal-id';
12+
const TEST_MODULE = 'myModal';
13+
14+
describe('GlModalVuex', () => {
15+
let wrapper;
16+
let state;
17+
let actions;
18+
19+
const factory = (options = {}) => {
20+
const store = new Vuex.Store({
21+
modules: {
22+
[TEST_MODULE]: {
23+
namespaced: true,
24+
state,
25+
actions,
26+
},
27+
},
28+
});
29+
30+
const propsData = {
31+
modalId: TEST_MODAL_ID,
32+
modalModule: TEST_MODULE,
33+
...options.propsData,
34+
};
35+
36+
wrapper = shallowMount(localVue.extend(GlModalVuex), {
37+
...options,
38+
localVue,
39+
store,
40+
propsData,
41+
});
42+
};
43+
44+
beforeEach(() => {
45+
state = createState();
46+
47+
actions = {
48+
show: jasmine.createSpy('show'),
49+
hide: jasmine.createSpy('hide'),
50+
};
51+
});
52+
53+
it('renders gl-modal', () => {
54+
factory({
55+
slots: {
56+
default: `<div>${TEST_SLOT}</div>`,
57+
},
58+
});
59+
const glModal = wrapper.find(GlModal);
60+
61+
expect(glModal.props('modalId')).toBe(TEST_MODAL_ID);
62+
expect(glModal.text()).toContain(TEST_SLOT);
63+
});
64+
65+
it('passes props through to gl-modal', () => {
66+
const title = 'Test Title';
67+
const okVariant = 'success';
68+
69+
factory({
70+
propsData: {
71+
title,
72+
okTitle: title,
73+
okVariant,
74+
},
75+
});
76+
const glModal = wrapper.find(GlModal);
77+
78+
expect(glModal.attributes('title')).toEqual(title);
79+
expect(glModal.attributes('oktitle')).toEqual(title);
80+
expect(glModal.attributes('okvariant')).toEqual(okVariant);
81+
});
82+
83+
it('passes listeners through to gl-modal', () => {
84+
const ok = jasmine.createSpy('ok');
85+
86+
factory({
87+
listeners: { ok },
88+
});
89+
90+
const glModal = wrapper.find(GlModal);
91+
glModal.vm.$emit('ok');
92+
93+
expect(ok).toHaveBeenCalledTimes(1);
94+
});
95+
96+
it('calls vuex action on show', () => {
97+
expect(actions.show).not.toHaveBeenCalled();
98+
99+
factory();
100+
101+
const glModal = wrapper.find(GlModal);
102+
glModal.vm.$emit('shown');
103+
104+
expect(actions.show).toHaveBeenCalledTimes(1);
105+
});
106+
107+
it('calls vuex action on hide', () => {
108+
expect(actions.hide).not.toHaveBeenCalled();
109+
110+
factory();
111+
112+
const glModal = wrapper.find(GlModal);
113+
glModal.vm.$emit('hidden');
114+
115+
expect(actions.hide).toHaveBeenCalledTimes(1);
116+
});
117+
118+
it('calls bootstrap show when isVisible changes', done => {
119+
state.isVisible = false;
120+
121+
factory();
122+
const rootEmit = spyOn(wrapper.vm.$root, '$emit');
123+
124+
state.isVisible = true;
125+
126+
localVue
127+
.nextTick()
128+
.then(() => {
129+
expect(rootEmit).toHaveBeenCalledWith('bv::show::modal', TEST_MODAL_ID);
130+
})
131+
.then(done)
132+
.catch(done.fail);
133+
});
134+
135+
it('calls bootstrap hide when isVisible changes', done => {
136+
state.isVisible = true;
137+
138+
factory();
139+
const rootEmit = spyOn(wrapper.vm.$root, '$emit');
140+
141+
state.isVisible = false;
142+
143+
localVue
144+
.nextTick()
145+
.then(() => {
146+
expect(rootEmit).toHaveBeenCalledWith('bv::hide::modal', TEST_MODAL_ID);
147+
})
148+
.then(done)
149+
.catch(done.fail);
150+
});
151+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as types from '~/vuex_shared/modules/modal/mutation_types';
2+
import * as actions from '~/vuex_shared/modules/modal/actions';
3+
import testAction from 'spec/helpers/vuex_action_helper';
4+
5+
describe('Vuex ModalModule actions', () => {
6+
describe('open', () => {
7+
it('works', done => {
8+
const data = { id: 7 };
9+
10+
testAction(actions.open, data, {}, [{ type: types.OPEN, payload: data }], [], done);
11+
});
12+
});
13+
14+
describe('close', () => {
15+
it('works', done => {
16+
testAction(actions.close, null, {}, [{ type: types.CLOSE }], [], done);
17+
});
18+
});
19+
20+
describe('show', () => {
21+
it('works', done => {
22+
testAction(actions.show, null, {}, [{ type: types.SHOW }], [], done);
23+
});
24+
});
25+
26+
describe('hide', () => {
27+
it('works', done => {
28+
testAction(actions.hide, null, {}, [{ type: types.HIDE }], [], done);
29+
});
30+
});
31+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import mutations from '~/vuex_shared/modules/modal/mutations';
2+
import * as types from '~/vuex_shared/modules/modal/mutation_types';
3+
4+
describe('Vuex ModalModule mutations', () => {
5+
describe(types.SHOW, () => {
6+
it('sets isVisible to true', () => {
7+
const state = {
8+
isVisible: false,
9+
};
10+
11+
mutations[types.SHOW](state);
12+
13+
expect(state).toEqual({
14+
isVisible: true,
15+
});
16+
});
17+
});
18+
19+
describe(types.HIDE, () => {
20+
it('sets isVisible to false', () => {
21+
const state = {
22+
isVisible: true,
23+
};
24+
25+
mutations[types.HIDE](state);
26+
27+
expect(state).toEqual({
28+
isVisible: false,
29+
});
30+
});
31+
});
32+
33+
describe(types.OPEN, () => {
34+
it('sets data and sets isVisible to true', () => {
35+
const data = { id: 7 };
36+
const state = {
37+
isVisible: false,
38+
data: null,
39+
};
40+
41+
mutations[types.OPEN](state, data);
42+
43+
expect(state).toEqual({
44+
isVisible: true,
45+
data,
46+
});
47+
});
48+
});
49+
});

0 commit comments

Comments
 (0)