Commit d58e8168 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '216865-confirm-leave-site' into 'master'

Display confirm modal before leaving Static Site Editor

Closes #216865

See merge request gitlab-org/gitlab!33103
parents d150fcb2 47541ff5
...@@ -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