Commit 47541ff5 authored by Enrique Alcántara's avatar Enrique Alcántara Committed by Nicolò Maria Mezzopera

Display confirm modal before leaving SSE

When the user tries to leave the Static Site Editor,
display a confirmation dialog indicating that they
may lose their changes
parent 5b65045d
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue'; import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
import PublishToolbar from './publish_toolbar.vue'; import PublishToolbar from './publish_toolbar.vue';
import EditHeader from './edit_header.vue'; import EditHeader from './edit_header.vue';
import UnsavedChangesConfirmDialog from './unsaved_changes_confirm_dialog.vue';
export default { export default {
components: { components: {
RichContentEditor, RichContentEditor,
PublishToolbar, PublishToolbar,
EditHeader, EditHeader,
UnsavedChangesConfirmDialog,
}, },
props: { props: {
title: { title: {
...@@ -50,6 +52,7 @@ export default { ...@@ -50,6 +52,7 @@ export default {
<div class="d-flex flex-grow-1 flex-column h-100"> <div class="d-flex flex-grow-1 flex-column h-100">
<edit-header class="py-2" :title="title" /> <edit-header class="py-2" :title="title" />
<rich-content-editor v-model="editableContent" class="mb-9 h-100" /> <rich-content-editor v-model="editableContent" class="mb-9 h-100" />
<unsaved-changes-confirm-dialog :modified="modified" />
<publish-toolbar <publish-toolbar
class="gl-fixed gl-left-0 gl-bottom-0 gl-w-full" class="gl-fixed gl-left-0 gl-bottom-0 gl-w-full"
:return-url="returnUrl" :return-url="returnUrl"
......
<script>
export default {
props: {
modified: {
type: Boolean,
required: false,
default: false,
},
},
created() {
window.addEventListener('beforeunload', this.requestConfirmation);
},
destroyed() {
window.removeEventListener('beforeunload', this.requestConfirmation);
},
methods: {
requestConfirmation(e) {
if (this.modified) {
e.preventDefault();
// eslint-disable-next-line no-param-reassign
e.returnValue = '';
}
},
},
render: () => null,
};
</script>
---
title: Display confirmation modal when user exits SSE and there are unsaved changes
merge_request: 33103
author:
type: added
...@@ -5,6 +5,7 @@ import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_ ...@@ -5,6 +5,7 @@ import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_
import EditArea from '~/static_site_editor/components/edit_area.vue'; import EditArea from '~/static_site_editor/components/edit_area.vue';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
import EditHeader from '~/static_site_editor/components/edit_header.vue'; import EditHeader from '~/static_site_editor/components/edit_header.vue';
import UnsavedChangesConfirmDialog from '~/static_site_editor/components/unsaved_changes_confirm_dialog.vue';
import { sourceContentTitle as title, sourceContent as content, returnUrl } from '../mock_data'; import { sourceContentTitle as title, sourceContent as content, returnUrl } from '../mock_data';
...@@ -28,6 +29,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => { ...@@ -28,6 +29,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
const findEditHeader = () => wrapper.find(EditHeader); const findEditHeader = () => wrapper.find(EditHeader);
const findRichContentEditor = () => wrapper.find(RichContentEditor); const findRichContentEditor = () => wrapper.find(RichContentEditor);
const findPublishToolbar = () => wrapper.find(PublishToolbar); const findPublishToolbar = () => wrapper.find(PublishToolbar);
const findUnsavedChangesConfirmDialog = () => wrapper.find(UnsavedChangesConfirmDialog);
beforeEach(() => { beforeEach(() => {
buildWrapper(); buildWrapper();
...@@ -49,9 +51,16 @@ describe('~/static_site_editor/components/edit_area.vue', () => { ...@@ -49,9 +51,16 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
it('renders publish toolbar', () => { it('renders publish toolbar', () => {
expect(findPublishToolbar().exists()).toBe(true); expect(findPublishToolbar().exists()).toBe(true);
expect(findPublishToolbar().props('returnUrl')).toBe(returnUrl); expect(findPublishToolbar().props()).toMatchObject({
expect(findPublishToolbar().props('savingChanges')).toBe(savingChanges); returnUrl,
expect(findPublishToolbar().props('saveable')).toBe(false); savingChanges,
saveable: false,
});
});
it('renders unsaved changes confirm dialog', () => {
expect(findUnsavedChangesConfirmDialog().exists()).toBe(true);
expect(findUnsavedChangesConfirmDialog().props('modified')).toBe(false);
}); });
describe('when content changes', () => { describe('when content changes', () => {
...@@ -61,10 +70,14 @@ describe('~/static_site_editor/components/edit_area.vue', () => { ...@@ -61,10 +70,14 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
it('sets publish toolbar as saveable when content changes', () => { it('sets publish toolbar as saveable', () => {
expect(findPublishToolbar().props('saveable')).toBe(true); expect(findPublishToolbar().props('saveable')).toBe(true);
}); });
it('sets unsaved changes confirm dialog as modified', () => {
expect(findUnsavedChangesConfirmDialog().props('modified')).toBe(true);
});
it('sets publish toolbar as not saveable when content changes are rollback', () => { it('sets publish toolbar as not saveable when content changes are rollback', () => {
findRichContentEditor().vm.$emit('input', content); findRichContentEditor().vm.$emit('input', content);
......
import { shallowMount } from '@vue/test-utils';
import UnsavedChangesConfirmDialog from '~/static_site_editor/components/unsaved_changes_confirm_dialog.vue';
describe('static_site_editor/components/unsaved_changes_confirm_dialog', () => {
let wrapper;
let event;
let returnValueSetter;
const buildWrapper = (propsData = {}) => {
wrapper = shallowMount(UnsavedChangesConfirmDialog, {
propsData,
});
};
beforeEach(() => {
event = new Event('beforeunload');
jest.spyOn(event, 'preventDefault');
returnValueSetter = jest.spyOn(event, 'returnValue', 'set');
});
afterEach(() => {
event.preventDefault.mockRestore();
returnValueSetter.mockRestore();
wrapper.destroy();
});
it('displays confirmation dialog when modified = true', () => {
buildWrapper({ modified: true });
window.dispatchEvent(event);
expect(event.preventDefault).toHaveBeenCalled();
expect(returnValueSetter).toHaveBeenCalledWith('');
});
it('does not display confirmation dialog when modified = false', () => {
buildWrapper();
window.dispatchEvent(event);
expect(event.preventDefault).not.toHaveBeenCalled();
expect(returnValueSetter).not.toHaveBeenCalled();
});
});
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