Add id to modal.vue to support data-toggle="modal"

This commit is contained in:
Winnie Hellmann 2018-01-03 10:21:17 +01:00
parent b1e1990ee2
commit 2c47f0924f
15 changed files with 169 additions and 116 deletions

View File

@ -45,11 +45,9 @@ export default {
onLeaveGroup() { onLeaveGroup() {
this.modalStatus = true; this.modalStatus = true;
}, },
leaveGroup(leaveConfirmed) { leaveGroup() {
this.modalStatus = false; this.modalStatus = false;
if (leaveConfirmed) { eventHub.$emit('leaveGroup', this.group, this.parentGroup);
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
}
}, },
}, },
}; };

View File

@ -32,10 +32,10 @@
methods: { methods: {
createNewItem(type) { createNewItem(type) {
this.modalType = type; this.modalType = type;
this.toggleModalOpen(); this.openModal = true;
}, },
toggleModalOpen() { hideModal() {
this.openModal = !this.openModal; this.openModal = false;
}, },
}, },
}; };
@ -95,7 +95,7 @@
:branch-id="branch" :branch-id="branch"
:path="path" :path="path"
:parent="parent" :parent="parent"
@toggle="toggleModalOpen" @hide="hideModal"
/> />
</div> </div>
</template> </template>

View File

@ -43,10 +43,10 @@
type: this.type, type: this.type,
}); });
this.toggleModalOpen(); this.hideModal();
}, },
toggleModalOpen() { hideModal() {
this.$emit('toggle'); this.$emit('hide');
}, },
}, },
computed: { computed: {
@ -86,7 +86,7 @@
:title="modalTitle" :title="modalTitle"
:primary-button-label="buttonLabel" :primary-button-label="buttonLabel"
kind="success" kind="success"
@toggle="toggleModalOpen" @cancel="hideModal"
@submit="createEntryInStore" @submit="createEntryInStore"
> >
<form <form

View File

@ -110,7 +110,7 @@ export default {
kind="primary" kind="primary"
:title="__('Branch has changed')" :title="__('Branch has changed')"
:text="__('This branch has changed since you started editing. Would you like to create a new branch?')" :text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
@toggle="showNewBranchModal = false" @cancel="showNewBranchModal = false"
@submit="makeCommit(true)" @submit="makeCommit(true)"
/> />
<commit-files-list <commit-files-list

View File

@ -50,7 +50,7 @@ export default {
kind="warning" kind="warning"
:title="__('Are you sure?')" :title="__('Are you sure?')"
:text="__('Are you sure you want to discard your changes?')" :text="__('Are you sure you want to discard your changes?')"
@toggle="closeDiscardPopup" @cancel="closeDiscardPopup"
@submit="toggleEditMode(true)" @submit="toggleEditMode(true)"
/> />
</div> </div>

View File

@ -1,7 +1,7 @@
<script> <script>
import modal from '../../../vue_shared/components/modal.vue'; import modal from '~/vue_shared/components/modal.vue';
import { __, s__, sprintf } from '../../../locale'; import { __, s__, sprintf } from '~/locale';
import csrf from '../../../lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
export default { export default {
props: { props: {
@ -22,7 +22,6 @@
return { return {
enteredPassword: '', enteredPassword: '',
enteredUsername: '', enteredUsername: '',
isOpen: false,
}; };
}, },
components: { components: {
@ -69,78 +68,58 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
return this.enteredUsername === this.username; return this.enteredUsername === this.username;
}, },
onSubmit(status) { onSubmit() {
if (status) { this.$refs.form.submit();
if (!this.canSubmit()) {
return;
}
this.$refs.form.submit();
}
this.toggleOpen(false);
},
toggleOpen(isOpen) {
this.isOpen = isOpen;
}, },
}, },
}; };
</script> </script>
<template> <template>
<div> <modal
<modal id="delete-account-modal"
v-if="isOpen" :title="s__('Profiles|Delete your account?')"
:title="s__('Profiles|Delete your account?')" :text="text"
:text="text" kind="danger"
:kind="`danger ${!canSubmit() && 'disabled'}`" :primary-button-label="s__('Profiles|Delete account')"
:primary-button-label="s__('Profiles|Delete account')" @submit="onSubmit"
@toggle="toggleOpen" :submit-disabled="!canSubmit()">
@submit="onSubmit">
<template slot="body" slot-scope="props"> <template slot="body" slot-scope="props">
<p v-html="props.text"></p> <p v-html="props.text"></p>
<form <form
ref="form" ref="form"
:action="actionUrl" :action="actionUrl"
method="post"> method="post">
<input <input
type="hidden" type="hidden"
name="_method" name="_method"
value="delete" /> value="delete" />
<input <input
type="hidden" type="hidden"
name="authenticity_token" name="authenticity_token"
:value="csrfToken" /> :value="csrfToken" />
<p id="input-label" v-html="inputLabel"></p> <p id="input-label" v-html="inputLabel"></p>
<input <input
v-if="confirmWithPassword" v-if="confirmWithPassword"
name="password" name="password"
class="form-control" class="form-control"
type="password" type="password"
v-model="enteredPassword" v-model="enteredPassword"
aria-labelledby="input-label" /> aria-labelledby="input-label" />
<input <input
v-else v-else
name="username" name="username"
class="form-control" class="form-control"
type="text" type="text"
v-model="enteredUsername" v-model="enteredUsername"
aria-labelledby="input-label" /> aria-labelledby="input-label" />
</form> </form>
</template> </template>
</modal> </modal>
<button
type="button"
class="btn btn-danger"
@click="toggleOpen(true)">
{{ s__('Profiles|Delete account') }}
</button>
</div>
</template> </template>

View File

@ -1,7 +1,12 @@
import Vue from 'vue'; import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import deleteAccountModal from './components/delete_account_modal.vue'; import deleteAccountModal from './components/delete_account_modal.vue';
Vue.use(Translate);
const deleteAccountButton = document.getElementById('delete-account-button');
const deleteAccountModalEl = document.getElementById('delete-account-modal'); const deleteAccountModalEl = document.getElementById('delete-account-modal');
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
@ -9,6 +14,9 @@ new Vue({
components: { components: {
deleteAccountModal, deleteAccountModal,
}, },
mounted() {
deleteAccountButton.classList.remove('disabled');
},
render(createElement) { render(createElement) {
return createElement('delete-account-modal', { return createElement('delete-account-modal', {
props: { props: {

View File

@ -3,6 +3,10 @@ export default {
name: 'modal', name: 'modal',
props: { props: {
id: {
type: String,
required: false,
},
title: { title: {
type: String, type: String,
required: false, required: false,
@ -62,11 +66,11 @@ export default {
}, },
methods: { methods: {
close() { emitCancel(event) {
this.$emit('toggle', false); this.$emit('cancel', event);
}, },
emitSubmit(status) { emitSubmit(event) {
this.$emit('submit', status); this.$emit('submit', event);
}, },
}, },
}; };
@ -75,7 +79,9 @@ export default {
<template> <template>
<div class="modal-open"> <div class="modal-open">
<div <div
class="modal show" :id="id"
class="modal"
:class="id ? '' : 'show'"
role="dialog" role="dialog"
tabindex="-1" tabindex="-1"
> >
@ -93,7 +99,8 @@ export default {
<button <button
type="button" type="button"
class="close pull-right" class="close pull-right"
@click="close" @click="emitCancel($event)"
data-dismiss="modal"
aria-label="Close" aria-label="Close"
> >
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
@ -110,7 +117,8 @@ export default {
type="button" type="button"
class="btn pull-left" class="btn pull-left"
:class="btnCancelKindClass" :class="btnCancelKindClass"
@click="close"> @click="emitCancel($event)"
data-dismiss="modal">
{{ closeButtonLabel }} {{ closeButtonLabel }}
</button> </button>
<button <button
@ -119,13 +127,17 @@ export default {
class="btn pull-right js-primary-button" class="btn pull-right js-primary-button"
:disabled="submitDisabled" :disabled="submitDisabled"
:class="btnKindClass" :class="btnKindClass"
@click="emitSubmit(true)"> @click="emitSubmit($event)"
data-dismiss="modal">
{{ primaryButtonLabel }} {{ primaryButtonLabel }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-backdrop fade in" /> <div
v-if="!id"
class="modal-backdrop fade in">
</div>
</div> </div>
</template> </template>

View File

@ -70,7 +70,7 @@ export default {
class="recaptcha-modal js-recaptcha-modal" class="recaptcha-modal js-recaptcha-modal"
:hide-footer="true" :hide-footer="true"
:title="__('Please solve the reCAPTCHA')" :title="__('Please solve the reCAPTCHA')"
@toggle="close" @cancel="close"
> >
<div slot="body"> <div slot="body">
<p> <p>

View File

@ -84,11 +84,13 @@
= s_('Profiles|Deleting an account has the following effects:') = s_('Profiles|Deleting an account has the following effects:')
= render 'users/deletion_guidance', user: current_user = render 'users/deletion_guidance', user: current_user
%button#delete-account-button.btn.btn-danger.disabled{ data: { toggle: 'modal',
target: '#delete-account-modal' } }
= s_('Profiles|Delete account')
#delete-account-modal{ data: { action_url: user_registration_path, #delete-account-modal{ data: { action_url: user_registration_path,
confirm_with_password: ('true' if current_user.confirm_deletion_with_password?), confirm_with_password: ('true' if current_user.confirm_deletion_with_password?),
username: current_user.username } } username: current_user.username } }
%button.btn.btn-danger.disabled
= s_('Profiles|Delete account')
- else - else
- if @user.solo_owned_groups.present? - if @user.solo_owned_groups.present?
%p %p

View File

@ -0,0 +1,5 @@
---
title: Add id to modal.vue to support data-toggle="modal"
merge_request: 16189
author:
type: other

View File

@ -47,18 +47,12 @@ describe('ItemActionsComponent', () => {
it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => {
spyOn(eventHub, '$emit'); spyOn(eventHub, '$emit');
vm.modalStatus = true; vm.modalStatus = true;
vm.leaveGroup(true);
vm.leaveGroup();
expect(vm.modalStatus).toBeFalsy(); expect(vm.modalStatus).toBeFalsy();
expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup); expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup);
}); });
it('should change `modalStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => {
spyOn(eventHub, '$emit');
vm.modalStatus = true;
vm.leaveGroup(false);
expect(vm.modalStatus).toBeFalsy();
expect(eventHub.$emit).not.toHaveBeenCalled();
});
}); });
}); });

View File

@ -51,7 +51,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(vm.enteredPassword).toBe(input.value); expect(vm.enteredPassword).toBe(input.value);
expect(submitButton).toHaveClass('disabled'); expect(submitButton).toHaveAttr('disabled', 'disabled');
submitButton.click(); submitButton.click();
expect(form.submit).not.toHaveBeenCalled(); expect(form.submit).not.toHaveBeenCalled();
}) })
@ -68,7 +68,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(vm.enteredPassword).toBe(input.value); expect(vm.enteredPassword).toBe(input.value);
expect(submitButton).not.toHaveClass('disabled'); expect(submitButton).not.toHaveAttr('disabled', 'disabled');
submitButton.click(); submitButton.click();
expect(form.submit).toHaveBeenCalled(); expect(form.submit).toHaveBeenCalled();
}) })
@ -101,7 +101,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(vm.enteredUsername).toBe(input.value); expect(vm.enteredUsername).toBe(input.value);
expect(submitButton).toHaveClass('disabled'); expect(submitButton).toHaveAttr('disabled', 'disabled');
submitButton.click(); submitButton.click();
expect(form.submit).not.toHaveBeenCalled(); expect(form.submit).not.toHaveBeenCalled();
}) })
@ -118,7 +118,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(vm.enteredUsername).toBe(input.value); expect(vm.enteredUsername).toBe(input.value);
expect(submitButton).not.toHaveClass('disabled'); expect(submitButton).not.toHaveAttr('disabled', 'disabled');
submitButton.click(); submitButton.click();
expect(form.submit).toHaveBeenCalled(); expect(form.submit).toHaveBeenCalled();
}) })

View File

@ -57,15 +57,16 @@ describe('new dropdown component', () => {
}); });
}); });
describe('toggleModalOpen', () => { describe('hideModal', () => {
beforeAll((done) => {
vm.openModal = true;
Vue.nextTick(done);
});
it('closes modal after toggling', (done) => { it('closes modal after toggling', (done) => {
vm.toggleModalOpen(); vm.hideModal();
Vue.nextTick() Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('.modal')).not.toBeNull();
})
.then(vm.toggleModalOpen)
.then(() => { .then(() => {
expect(vm.$el.querySelector('.modal')).toBeNull(); expect(vm.$el.querySelector('.modal')).toBeNull();
}) })

View File

@ -2,11 +2,65 @@ import Vue from 'vue';
import modal from '~/vue_shared/components/modal.vue'; import modal from '~/vue_shared/components/modal.vue';
import mountComponent from '../../helpers/vue_mount_component_helper'; import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Modal', () => { const modalComponent = Vue.extend(modal);
it('does not render a primary button if no primaryButtonLabel', () => {
const modalComponent = Vue.extend(modal);
const vm = mountComponent(modalComponent);
expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); describe('Modal', () => {
let vm;
afterEach(() => {
vm.$destroy();
});
describe('props', () => {
describe('without primaryButtonLabel', () => {
beforeEach(() => {
vm = mountComponent(modalComponent, {
primaryButtonLabel: null,
});
});
it('does not render a primary button', () => {
expect(vm.$el.querySelector('.js-primary-button')).toBeNull();
});
});
describe('with id', () => {
it('does not render a primary button', () => {
beforeEach(() => {
vm = mountComponent(modalComponent, {
id: 'my-modal',
});
});
it('assigns the id to the modal', () => {
expect(vm.$el.querySelector('#my-modal.modal')).not.toBeNull();
});
it('does not show the modal immediately', () => {
expect(vm.$el.querySelector('#my-modal.modal')).not.toHaveClass('show');
});
it('does not show a backdrop', () => {
expect(vm.$el.querySelector('modal-backdrop')).toBeNull();
});
});
});
it('works with data-toggle="modal"', (done) => {
setFixtures(`
<button id="modal-button" data-toggle="modal" data-target="#my-modal"></button>
<div id="modal-container"></div>
`);
const modalContainer = document.getElementById('modal-container');
const modalButton = document.getElementById('modal-button');
vm = mountComponent(modalComponent, {
id: 'my-modal',
}, modalContainer);
const modalElement = vm.$el.querySelector('#my-modal');
$(modalElement).on('shown.bs.modal', () => done());
modalButton.click();
});
}); });
}); });