Commit 15d80904 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'mw-delete-label-modal-migration' into 'master'

Migrate delete label modal to Vue

See merge request gitlab-org/gitlab!58131
parents f3440c99 88d954ab
import Vue from 'vue';
import DeleteLabelModal from '~/vue_shared/components/delete_label_modal.vue';
const mountDeleteLabelModal = (optionalProps) =>
new Vue({
render(h) {
return h(DeleteLabelModal, {
props: {
selector: '.js-delete-label-modal-button',
...optionalProps,
},
});
},
}).$mount();
export default (optionalProps = {}) => mountDeleteLabelModal(optionalProps);
import initDeleteLabelModal from '~/delete_label_modal';
import initLabels from '~/init_labels';
initLabels();
initDeleteLabelModal();
import Vue from 'vue';
import initDeleteLabelModal from '~/delete_label_modal';
import initLabels from '~/init_labels';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import Translate from '~/vue_shared/translate';
......@@ -9,6 +10,7 @@ Vue.use(Translate);
const initLabelIndex = () => {
initLabels();
initDeleteLabelModal();
const onRequestFinished = ({ labelUrl, successful }) => {
const button = document.querySelector(
......
<script>
import { GlModal, GlSprintf, GlButton } from '@gitlab/ui';
import { uniqueId } from 'lodash';
export default {
components: {
GlModal,
GlSprintf,
GlButton,
},
props: {
selector: {
type: String,
required: true,
},
},
data() {
return {
labelName: '',
subjectName: '',
destroyPath: '',
modalId: uniqueId('modal-delete-label-'),
};
},
mounted() {
document.querySelectorAll(this.selector).forEach((button) => {
button.addEventListener('click', (e) => {
e.preventDefault();
const { labelName, subjectName, destroyPath } = button.dataset;
this.labelName = labelName;
this.subjectName = subjectName;
this.destroyPath = destroyPath;
this.openModal();
});
});
},
methods: {
openModal() {
this.$refs.modal.show();
},
closeModal() {
this.$refs.modal.hide();
},
},
};
</script>
<template>
<gl-modal ref="modal" :modal-id="modalId">
<template #modal-title>
<gl-sprintf :message="__('Delete label: %{labelName}')">
<template #labelName>
{{ labelName }}
</template>
</gl-sprintf>
</template>
<gl-sprintf
:message="
__(
`%{strongStart}${labelName}%{strongEnd} will be permanently deleted from ${subjectName}. This cannot be undone.`,
)
"
>
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
</gl-sprintf>
<template #modal-footer>
<gl-button category="secondary" @click="closeModal">{{ __('Cancel') }}</gl-button>
<gl-button
category="primary"
variant="danger"
:href="destroyPath"
data-method="delete"
data-testid="delete-button"
>{{ __('Delete label') }}</gl-button
>
</template>
</gl-modal>
</template>
.modal{ id: "modal-delete-label-#{label.id}", tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
%h3.page-title= _('Delete label: %{label_name} ?') % { label_name: label.name }
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
.modal-body
%p
= html_escape(_('%{label_name} %{span_open}will be permanently deleted from %{subject_name}. This cannot be undone.%{span_close}')) % { label_name: tag.strong(label.name), subject_name: label.subject_name, span_open: '<span>'.html_safe, span_close: '</span>'.html_safe }
.modal-footer
%a{ href: '#', data: { dismiss: 'modal' }, class: 'gl-button btn btn-default' }= _('Cancel')
= link_to _('Delete label'),
label.destroy_path,
title: _('Delete'),
method: :delete,
class: 'gl-button btn btn-danger'
......@@ -36,10 +36,10 @@
label_text_color: label.text_color,
group_name: label.project.group.name } }
= _('Promote to group label')
- if can?(current_user, :admin_label, label)
%li
%span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } }
%button.text-danger.remove-row{ type: 'button' }= _('Delete')
%span
%button.text-danger.remove-row.js-delete-label-modal-button{ type: 'button', data: { label_name: label.name, subject_name: label.subject_name, destroy_path: label.destroy_path } }
= _('Delete')
- if current_user
%li.inline.label-subscription
- if label.can_subscribe_to_label_in_different_levels?
......@@ -61,5 +61,3 @@
- else
%button.gl-button.js-subscribe-button.label-subscribe-button.btn.btn-default.gl-ml-3{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
%span= label_subscription_toggle_button_text(label, @project)
= render 'shared/delete_label_modal', label: label
......@@ -623,9 +623,6 @@ msgstr ""
msgid "%{label_for_message} unavailable"
msgstr ""
msgid "%{label_name} %{span_open}will be permanently deleted from %{subject_name}. This cannot be undone.%{span_close}"
msgstr ""
msgid "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites."
msgstr ""
......@@ -10229,7 +10226,7 @@ msgstr ""
msgid "Delete label"
msgstr ""
msgid "Delete label: %{label_name} ?"
msgid "Delete label: %{labelName}"
msgstr ""
msgid "Delete pipeline"
......
......@@ -18,17 +18,17 @@ RSpec.describe "User removes labels" do
visit(project_labels_path(project))
end
it "removes label" do
it "removes label", :js do
page.within(".other-labels") do
page.first(".label-list-item") do
first('.js-label-options-dropdown').click
first(".remove-row").click
end
end
expect(page).to have_content("#{label.title} will be permanently deleted from #{project.name}. This cannot be undone.")
first(:link, "Delete label").click
end
expect(page).to have_content("Label was removed").and have_no_content(label.title)
end
......
import { TEST_HOST } from 'helpers/test_constants';
import initDeleteLabelModal from '~/delete_label_modal';
describe('DeleteLabelModal', () => {
const buttons = [
{
labelName: 'label 1',
subjectName: 'GitLab Org',
destroyPath: `${TEST_HOST}/1`,
},
{
labelName: 'label 2',
subjectName: 'GitLab Org',
destroyPath: `${TEST_HOST}/2`,
},
];
beforeEach(() => {
const buttonContainer = document.createElement('div');
buttons.forEach((x) => {
const button = document.createElement('button');
button.setAttribute('class', 'js-delete-label-modal-button');
button.setAttribute('data-label-name', x.labelName);
button.setAttribute('data-subject-name', x.subjectName);
button.setAttribute('data-destroy-path', x.destroyPath);
button.innerHTML = 'Action';
buttonContainer.appendChild(button);
});
document.body.appendChild(buttonContainer);
});
afterEach(() => {
document.body.innerHTML = '';
});
const findJsHooks = () => document.querySelectorAll('.js-delete-label-modal-button');
const findModal = () => document.querySelector('.gl-modal');
it('starts with only js-containers', () => {
expect(findJsHooks()).toHaveLength(buttons.length);
expect(findModal()).not.toExist();
});
describe('when first button clicked', () => {
beforeEach(() => {
initDeleteLabelModal();
findJsHooks().item(0).click();
});
it('does not replace js-containers with GlModal', () => {
expect(findJsHooks()).toHaveLength(buttons.length);
});
it('renders GlModal', () => {
expect(findModal()).toExist();
});
});
describe.each`
index
${0}
${1}
`(`when multiple buttons exist`, ({ index }) => {
beforeEach(() => {
initDeleteLabelModal();
findJsHooks().item(index).click();
});
it('correct props are passed to gl-modal', () => {
expect(findModal().querySelector('.modal-title').innerHTML).toContain(
buttons[index].labelName,
);
expect(findModal().querySelector('.modal-body').innerHTML).toContain(
buttons[index].subjectName,
);
expect(findModal().querySelector('.modal-footer .btn-danger').href).toContain(
buttons[index].destroyPath,
);
});
});
});
import { GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import { TEST_HOST } from 'helpers/test_constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import DeleteLabelModal from '~/vue_shared/components/delete_label_modal.vue';
const MOCK_MODAL_DATA = {
labelName: 'label 1',
subjectName: 'GitLab Org',
destroyPath: `${TEST_HOST}/1`,
};
describe('vue_shared/components/delete_label_modal', () => {
let wrapper;
const createComponent = () => {
wrapper = extendedWrapper(
mount(DeleteLabelModal, {
propsData: {
selector: '.js-test-btn',
},
stubs: {
GlModal: stubComponent(GlModal, {
template:
'<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
}),
},
}),
);
};
afterEach(() => {
wrapper.destroy();
});
const findModal = () => wrapper.find(GlModal);
const findPrimaryModalButton = () => wrapper.findByTestId('delete-button');
describe('template', () => {
describe('when modal data is set', () => {
beforeEach(() => {
createComponent();
wrapper.vm.labelName = MOCK_MODAL_DATA.labelName;
wrapper.vm.subjectName = MOCK_MODAL_DATA.subjectName;
wrapper.vm.destroyPath = MOCK_MODAL_DATA.destroyPath;
});
it('renders GlModal', () => {
expect(findModal().exists()).toBe(true);
});
it('displays the label name and subject name', () => {
expect(findModal().text()).toContain(
`${MOCK_MODAL_DATA.labelName} will be permanently deleted from ${MOCK_MODAL_DATA.subjectName}. This cannot be undone`,
);
});
it('passes the destroyPath to the button', () => {
expect(findPrimaryModalButton().attributes('href')).toBe(MOCK_MODAL_DATA.destroyPath);
});
});
});
});
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