From 708df374f598dd56901e8334dc94a98d423f6ee1 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Wed, 2 Jan 2019 12:34:19 -0600 Subject: 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. --- .../vue_shared/components/gl_modal_vuex.vue | 69 ++++++++++ .../vuex_shared/modules/modal/actions.js | 17 +++ .../javascripts/vuex_shared/modules/modal/index.js | 10 ++ .../vuex_shared/modules/modal/mutation_types.js | 4 + .../vuex_shared/modules/modal/mutations.js | 18 +++ .../javascripts/vuex_shared/modules/modal/state.js | 4 + .../vue_shared/components/gl_modal_vuex_spec.js | 151 +++++++++++++++++++++ .../vuex_shared/modules/modal/actions_spec.js | 31 +++++ .../vuex_shared/modules/modal/mutations_spec.js | 49 +++++++ 9 files changed, 353 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue create mode 100644 app/assets/javascripts/vuex_shared/modules/modal/actions.js create mode 100644 app/assets/javascripts/vuex_shared/modules/modal/index.js create mode 100644 app/assets/javascripts/vuex_shared/modules/modal/mutation_types.js create mode 100644 app/assets/javascripts/vuex_shared/modules/modal/mutations.js create mode 100644 app/assets/javascripts/vuex_shared/modules/modal/state.js create mode 100644 spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js create mode 100644 spec/javascripts/vuex_shared/modules/modal/actions_spec.js create mode 100644 spec/javascripts/vuex_shared/modules/modal/mutations_spec.js diff --git a/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue new file mode 100644 index 00000000000..df6fadf10cd --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue @@ -0,0 +1,69 @@ + + + diff --git a/app/assets/javascripts/vuex_shared/modules/modal/actions.js b/app/assets/javascripts/vuex_shared/modules/modal/actions.js new file mode 100644 index 00000000000..552237e05c5 --- /dev/null +++ b/app/assets/javascripts/vuex_shared/modules/modal/actions.js @@ -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); +}; diff --git a/app/assets/javascripts/vuex_shared/modules/modal/index.js b/app/assets/javascripts/vuex_shared/modules/modal/index.js new file mode 100644 index 00000000000..c349d875c24 --- /dev/null +++ b/app/assets/javascripts/vuex_shared/modules/modal/index.js @@ -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, +}); diff --git a/app/assets/javascripts/vuex_shared/modules/modal/mutation_types.js b/app/assets/javascripts/vuex_shared/modules/modal/mutation_types.js new file mode 100644 index 00000000000..f8259736009 --- /dev/null +++ b/app/assets/javascripts/vuex_shared/modules/modal/mutation_types.js @@ -0,0 +1,4 @@ +export const HIDE = 'HIDE'; +export const SHOW = 'SHOW'; +export const OPEN = 'OPEN'; +export const CLOSE = 'CLOSE'; diff --git a/app/assets/javascripts/vuex_shared/modules/modal/mutations.js b/app/assets/javascripts/vuex_shared/modules/modal/mutations.js new file mode 100644 index 00000000000..9e96ae8b5a9 --- /dev/null +++ b/app/assets/javascripts/vuex_shared/modules/modal/mutations.js @@ -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; + }, +}; diff --git a/app/assets/javascripts/vuex_shared/modules/modal/state.js b/app/assets/javascripts/vuex_shared/modules/modal/state.js new file mode 100644 index 00000000000..5d0955aa9b0 --- /dev/null +++ b/app/assets/javascripts/vuex_shared/modules/modal/state.js @@ -0,0 +1,4 @@ +export default () => ({ + isVisible: false, + data: null, +}); diff --git a/spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js b/spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js new file mode 100644 index 00000000000..eb78d37db3e --- /dev/null +++ b/spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js @@ -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: `
${TEST_SLOT}
`, + }, + }); + 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); + }); +}); diff --git a/spec/javascripts/vuex_shared/modules/modal/actions_spec.js b/spec/javascripts/vuex_shared/modules/modal/actions_spec.js new file mode 100644 index 00000000000..04f64663ae4 --- /dev/null +++ b/spec/javascripts/vuex_shared/modules/modal/actions_spec.js @@ -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); + }); + }); +}); diff --git a/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js b/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js new file mode 100644 index 00000000000..d07f8ba1e65 --- /dev/null +++ b/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js @@ -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, + }); + }); + }); +}); -- cgit v1.2.1