Commit 09160f78 authored by Marcin Sedlak-Jakubowski's avatar Marcin Sedlak-Jakubowski Committed by Kushal Pandya

Migrate licenses confirmation modal to use Pajamas

This refactor the license delete confirmation modal
to migrate to GlModal and GlSprintf
parent ec218553
...@@ -7,6 +7,7 @@ import { ...@@ -7,6 +7,7 @@ import {
GlLoadingIcon, GlLoadingIcon,
GlIcon, GlIcon,
GlButton, GlButton,
GlModalDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { getIssueStatusFromLicenseStatus } from 'ee/vue_shared/license_compliance/store/utils'; import { getIssueStatusFromLicenseStatus } from 'ee/vue_shared/license_compliance/store/utils';
import { LICENSE_MANAGEMENT } from 'ee/vue_shared/license_compliance/store/constants'; import { LICENSE_MANAGEMENT } from 'ee/vue_shared/license_compliance/store/constants';
...@@ -30,6 +31,7 @@ export default { ...@@ -30,6 +31,7 @@ export default {
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
}, },
props: { props: {
...@@ -99,6 +101,7 @@ export default { ...@@ -99,6 +101,7 @@ export default {
</gl-dropdown> </gl-dropdown>
<gl-button <gl-button
v-gl-tooltip v-gl-tooltip
v-gl-modal.modal-license-delete-confirmation
:title="__('Remove license')" :title="__('Remove license')"
:aria-label="__('Remove license')" :aria-label="__('Remove license')"
:disabled="loading" :disabled="loading"
...@@ -106,7 +109,6 @@ export default { ...@@ -106,7 +109,6 @@ export default {
class="js-remove-button gl-ml-3" class="js-remove-button gl-ml-3"
category="tertiary" category="tertiary"
data-toggle="modal" data-toggle="modal"
data-target="#modal-license-delete-confirmation"
@click="setLicenseInModal(license)" @click="setLicenseInModal(license)"
/> />
</div> </div>
......
<script> <script>
/* eslint-disable vue/no-v-html */
import { escape } from 'lodash';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { LICENSE_MANAGEMENT } from 'ee/vue_shared/license_compliance/store/constants'; import { LICENSE_MANAGEMENT } from 'ee/vue_shared/license_compliance/store/constants';
import { s__, sprintf } from '~/locale'; import { GlModal, GlSprintf } from '@gitlab/ui';
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; import { s__, __ } from '~/locale';
export default { export default {
name: 'LicenseDeleteConfirmationModal', name: 'LicenseDeleteConfirmationModal',
components: { GlModal: DeprecatedModal2 }, components: { GlModal, GlSprintf },
computed: { computed: {
...mapState(LICENSE_MANAGEMENT, ['currentLicenseInModal']), ...mapState(LICENSE_MANAGEMENT, ['currentLicenseInModal']),
confirmationText() {
const name = `<strong>${escape(this.currentLicenseInModal.name)}</strong>`;
return sprintf(
s__('LicenseCompliance|You are about to remove the license, %{name}, from this project.'),
{ name },
false,
);
},
}, },
methods: { methods: {
...mapActions(LICENSE_MANAGEMENT, ['resetLicenseInModal', 'deleteLicense']), ...mapActions(LICENSE_MANAGEMENT, ['resetLicenseInModal', 'deleteLicense']),
}, },
modal: {
title: s__('LicenseCompliance|Remove license?'),
actionPrimary: {
text: s__('LicenseCompliance|Remove license'),
attributes: [{ variant: 'danger' }],
},
actionCancel: {
text: __('Cancel'),
},
},
}; };
</script> </script>
<template> <template>
<gl-modal <gl-modal
id="modal-license-delete-confirmation" modal-id="modal-license-delete-confirmation"
:header-title-text="s__('LicenseCompliance|Remove license?')" :title="$options.modal.title"
:footer-primary-button-text="s__('LicenseCompliance|Remove license')" :action-primary="$options.modal.actionPrimary"
footer-primary-button-variant="danger" :action-cancel="$options.modal.actionCancel"
@primary="deleteLicense(currentLicenseInModal)"
@cancel="resetLicenseInModal" @cancel="resetLicenseInModal"
@submit="deleteLicense(currentLicenseInModal)"
> >
<span v-if="currentLicenseInModal" v-html="confirmationText"></span> <gl-sprintf
v-if="currentLicenseInModal"
:message="
s__('LicenseCompliance|You are about to remove the license, %{name}, from this project.')
"
>
<template #name>
<strong>{{ currentLicenseInModal.name }}</strong>
</template>
</gl-sprintf>
</gl-modal> </gl-modal>
</template> </template>
---
title: Refactor license delete confirmation modal to use Pajamas
merge_request: 46149
author:
type: other
import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import DeleteConfirmationModal from 'ee/vue_shared/license_compliance/components/delete_confirmation_modal.vue'; import { GlModal, GlSprintf } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper'; import Component from 'ee/vue_shared/license_compliance/components/delete_confirmation_modal.vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { approvedLicense } from '../mock_data'; import { approvedLicense } from '../mock_data';
Vue.use(Vuex); const localVue = createLocalVue();
localVue.use(Vuex);
describe('DeleteConfirmationModal', () => { describe('DeleteConfirmationModal', () => {
const Component = Vue.extend(DeleteConfirmationModal);
let vm;
let store; let store;
let actions; let wrapper;
beforeEach(() => { const mockEvent = { preventDefault: jest.fn() };
actions = { const actions = {
resetLicenseInModal: jest.fn(), resetLicenseInModal: jest.fn(),
deleteLicense: jest.fn(), deleteLicense: jest.fn(),
}; };
store = new Vuex.Store({ const createStore = (initialState = {}) => {
return new Vuex.Store({
modules: { modules: {
licenseManagement: { licenseManagement: {
namespaced: true, namespaced: true,
state: { state: {
currentLicenseInModal: approvedLicense, currentLicenseInModal: approvedLicense,
...initialState,
}, },
actions, actions,
}, },
}, },
}); });
};
vm = mountComponentWithStore(Component, { store }); const createComponent = initialState => {
store = createStore(initialState);
wrapper = shallowMount(Component, {
localVue,
store,
stubs: {
GlModal,
GlSprintf,
},
}); });
};
afterEach(() => { const findModal = () => wrapper.find(GlModal);
vm.$destroy();
beforeEach(() => {
createComponent();
}); });
describe('computed', () => { afterEach(() => {
describe('confirmationText', () => { wrapper.destroy();
it('returns information text with current license name in bold', () => {
expect(vm.confirmationText).toBe(
`You are about to remove the license, <strong>${approvedLicense.name}</strong>, from this project.`,
);
}); });
it('escapes the license name', done => { describe('modal', () => {
const name = '<a href="#">BAD</a>'; it('should be loaded', () => {
const nameEscaped = '&lt;a href=&quot;#&quot;&gt;BAD&lt;/a&gt;'; expect(findModal().exists()).toBe(true);
});
store.replaceState({ it('should have Primary and Cancel actions', () => {
...store.state, expect(findModal().props()).toMatchObject({
licenseManagement: { actionPrimary: {
currentLicenseInModal: { text: 'Remove license',
...approvedLicense,
name,
}, },
actionCancel: {
text: 'Cancel',
}, },
}); });
});
Vue.nextTick() it('should have the confirmation text', () => {
.then(() => { expect(findModal().html()).toContain(
expect(vm.confirmationText).toBe( `You are about to remove the license, <strong>${approvedLicense.name}</strong>, from this project.`,
`You are about to remove the license, <strong>${nameEscaped}</strong>, from this project.`,
); );
})
.then(done)
.catch(done.fail);
});
});
}); });
describe('interaction', () => { it('should escape the confirmation text', () => {
describe('triggering resetLicenseInModal on canceling', () => { const name = '<a href="#">BAD</a>';
it('by clicking the cancel button', () => { const nameEscaped = '&lt;a href="#"&gt;BAD&lt;/a&gt;';
const linkEl = vm.$el.querySelector('.js-modal-cancel-action');
linkEl.click();
expect(actions.resetLicenseInModal).toHaveBeenCalled();
});
it('by clicking the X button', () => { const currentLicenseInModal = {
const linkEl = vm.$el.querySelector('.js-modal-close-action'); ...approvedLicense,
linkEl.click(); name,
};
expect(actions.resetLicenseInModal).toHaveBeenCalled(); createComponent({
}); currentLicenseInModal,
}); });
describe('triggering deleteLicense on canceling', () => { expect(findModal().html()).toContain(
it('by clicking the confirmation button', () => { `You are about to remove the license, <strong>${nameEscaped}</strong>, from this project`,
const linkEl = vm.$el.querySelector('.js-modal-primary-action');
linkEl.click();
expect(actions.deleteLicense).toHaveBeenCalledWith(
expect.any(Object),
store.state.licenseManagement.currentLicenseInModal,
); );
}); });
}); });
});
describe('template', () => {
it('renders modal title', () => {
const headerEl = vm.$el.querySelector('.modal-title');
expect(headerEl).not.toBeNull();
expect(headerEl.innerText.trim()).toBe('Remove license?');
});
it('renders button in modal footer', () => { describe('interaction', () => {
const footerButton = vm.$el.querySelector('.js-modal-primary-action'); it('triggering resetLicenseInModal on cancel', async () => {
findModal().vm.$emit('cancel', mockEvent);
expect(footerButton).not.toBeNull(); await wrapper.vm.$nextTick();
expect(footerButton.innerText.trim()).toBe('Remove license'); expect(actions.resetLicenseInModal).toHaveBeenCalled();
}); });
it('renders modal body', () => { it('triggering deleteLicense on cancel', async () => {
const modalBody = vm.$el.querySelector('.modal-body'); findModal().vm.$emit('primary', mockEvent);
await wrapper.vm.$nextTick();
expect(modalBody).not.toBeNull(); expect(actions.deleteLicense).toHaveBeenCalled();
expect(trimText(modalBody.innerText)).toBe(
`You are about to remove the license, ${approvedLicense.name}, from this project.`,
);
}); });
}); });
}); });
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