Commit a929c5f6 authored by Enrique Alcántara's avatar Enrique Alcántara Committed by Natalia Tepluhina

Static Site Editor Submit Changes toolbar

Display a toolbar in the bottom of the Static Site
Editor with a Submit Changes button that allow
to submit the changes in a page.
parent 8eb2ad31
<script>
import { GlNewButton } from '@gitlab/ui';
export default {
components: {
GlNewButton,
},
props: {
saveable: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
<div class="d-flex bg-light border-top justify-content-between align-items-center py-3 px-4">
<gl-new-button variant="success" :disabled="!saveable">
{{ __('Submit Changes') }}
</gl-new-button>
</div>
</template>
......@@ -3,27 +3,29 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import { GlSkeletonLoader } from '@gitlab/ui';
import EditArea from './edit_area.vue';
import Toolbar from './publish_toolbar.vue';
export default {
components: {
EditArea,
GlSkeletonLoader,
Toolbar,
},
computed: {
...mapState(['content', 'isLoadingContent']),
...mapGetters(['isContentLoaded']),
...mapGetters(['isContentLoaded', 'contentChanged']),
},
mounted() {
this.loadContent();
},
methods: {
...mapActions(['loadContent']),
...mapActions(['loadContent', 'setContent']),
},
};
</script>
<template>
<div class="d-flex justify-content-center h-100">
<div v-if="isLoadingContent" class="w-50 h-50 mt-2">
<div class="d-flex justify-content-center h-100 pt-2">
<div v-if="isLoadingContent" class="w-50 h-50">
<gl-skeleton-loader :width="500" :height="102">
<rect width="500" height="16" rx="4" />
<rect y="20" width="375" height="16" rx="4" />
......@@ -33,6 +35,13 @@ export default {
<rect x="410" y="40" width="90" height="16" rx="4" />
</gl-skeleton-loader>
</div>
<edit-area v-if="isContentLoaded" class="w-75 h-100 shadow-none" :value="content" />
<div v-if="isContentLoaded" class="d-flex flex-grow-1 flex-column">
<edit-area
class="w-75 h-100 shadow-none align-self-center"
:value="content"
@input="setContent"
/>
<toolbar :saveable="contentChanged" />
</div>
</div>
</template>
......@@ -15,4 +15,8 @@ export const loadContent = ({ commit, state: { sourcePath, projectId } }) => {
});
};
export const setContent = ({ commit }, content) => {
commit(mutationTypes.SET_CONTENT, content);
};
export default () => {};
// eslint-disable-next-line import/prefer-default-export
export const isContentLoaded = ({ content }) => Boolean(content);
export const isContentLoaded = ({ originalContent }) => Boolean(originalContent);
export const contentChanged = ({ originalContent, content }) => originalContent !== content;
export const LOAD_CONTENT = 'loadContent';
export const RECEIVE_CONTENT_SUCCESS = 'receiveContentSuccess';
export const RECEIVE_CONTENT_ERROR = 'receiveContentError';
export const SET_CONTENT = 'setContent';
......@@ -8,8 +8,12 @@ export default {
state.isLoadingContent = false;
state.title = title;
state.content = content;
state.originalContent = content;
},
[types.RECEIVE_CONTENT_ERROR](state) {
state.isLoadingContent = false;
},
[types.SET_CONTENT](state, content) {
state.content = content;
},
};
......@@ -3,7 +3,9 @@ const createState = (initialState = {}) => ({
sourcePath: null,
isLoadingContent: false,
isSavingChanges: false,
originalContent: '',
content: '',
title: '',
......
#static-site-editor{ data: {} }
#static-site-editor{ data: { project_id: '8', path: 'README.md' } }
......@@ -19388,6 +19388,9 @@ msgstr ""
msgid "Subkeys"
msgstr ""
msgid "Submit Changes"
msgstr ""
msgid "Submit a review"
msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlNewButton } from '@gitlab/ui';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
describe('Static Site Editor Toolbar', () => {
let wrapper;
const buildWrapper = (propsData = {}) => {
wrapper = shallowMount(PublishToolbar, {
propsData: {
saveable: false,
...propsData,
},
});
};
const findSaveChangesButton = () => wrapper.find(GlNewButton);
beforeEach(() => {
buildWrapper();
});
afterEach(() => {
wrapper.destroy();
});
it('renders Submit Changes button', () => {
expect(findSaveChangesButton().exists()).toBe(true);
});
it('disables Submit Changes button', () => {
expect(findSaveChangesButton().attributes('disabled')).toBe('true');
});
describe('when saveable', () => {
it('enables Submit Changes button', () => {
buildWrapper({ saveable: true });
expect(findSaveChangesButton().attributes('disabled')).toBeFalsy();
});
});
});
......@@ -7,6 +7,7 @@ import createState from '~/static_site_editor/store/state';
import StaticSiteEditor from '~/static_site_editor/components/static_site_editor.vue';
import EditArea from '~/static_site_editor/components/edit_area.vue';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
const localVue = createLocalVue();
......@@ -16,18 +17,31 @@ describe('StaticSiteEditor', () => {
let wrapper;
let store;
let loadContentActionMock;
let setContentActionMock;
const buildStore = ({ initialState, getters } = {}) => {
loadContentActionMock = jest.fn();
setContentActionMock = jest.fn();
store = new Vuex.Store({
state: createState(initialState),
getters: {
isContentLoaded: () => false,
contentChanged: () => false,
...getters,
},
actions: {
loadContent: loadContentActionMock,
setContent: setContentActionMock,
},
});
};
const buildContentLoadedStore = ({ initialState, getters } = {}) => {
buildStore({
initialState,
getters: {
isContentLoaded: () => true,
...getters,
},
});
};
......@@ -40,6 +54,8 @@ describe('StaticSiteEditor', () => {
};
const findEditArea = () => wrapper.find(EditArea);
const findPublishToolbar = () => wrapper.find(PublishToolbar);
const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader);
beforeEach(() => {
buildStore();
......@@ -54,6 +70,10 @@ describe('StaticSiteEditor', () => {
it('does not render edit area', () => {
expect(findEditArea().exists()).toBe(false);
});
it('does not render toolbar', () => {
expect(findPublishToolbar().exists()).toBe(false);
});
});
describe('when content is loaded', () => {
......@@ -68,19 +88,49 @@ describe('StaticSiteEditor', () => {
expect(findEditArea().exists()).toBe(true);
});
it('does not render skeleton loader', () => {
expect(findSkeletonLoader().exists()).toBe(false);
});
it('passes page content to edit area', () => {
expect(findEditArea().props('value')).toBe(content);
});
it('renders toolbar', () => {
expect(findPublishToolbar().exists()).toBe(true);
});
});
it('sets toolbar as saveable when content changes', () => {
buildContentLoadedStore({
getters: {
contentChanged: () => true,
},
});
buildWrapper();
expect(findPublishToolbar().props('saveable')).toBe(true);
});
it('displays skeleton loader while loading content', () => {
it('displays skeleton loader when loading content', () => {
buildStore({ initialState: { isLoadingContent: true } });
buildWrapper();
expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true);
expect(findSkeletonLoader().exists()).toBe(true);
});
it('dispatches load content action', () => {
expect(loadContentActionMock).toHaveBeenCalled();
});
it('dispatches setContent action when edit area emits input event', () => {
const content = 'new content';
buildContentLoadedStore();
buildWrapper();
findEditArea().vm.$emit('input', content);
expect(setContentActionMock).toHaveBeenCalledWith(expect.anything(), content, undefined);
});
});
......@@ -73,4 +73,15 @@ describe('Static Site Editor Store actions', () => {
});
});
});
describe('setContent', () => {
it('commits setContent mutation', () => {
testAction(actions.setContent, content, state, [
{
type: mutationTypes.SET_CONTENT,
payload: content,
},
]);
});
});
});
import createState from '~/static_site_editor/store/state';
import { isContentLoaded } from '~/static_site_editor/store/getters';
import { isContentLoaded, contentChanged } from '~/static_site_editor/store/getters';
import { sourceContent as content } from '../mock_data';
describe('Static Site Editor Store getters', () => {
describe('isContentLoaded', () => {
it('returns true when content is not empty', () => {
expect(isContentLoaded(createState({ content }))).toBe(true);
it('returns true when originalContent is not empty', () => {
expect(isContentLoaded(createState({ originalContent: content }))).toBe(true);
});
it('returns false when content is empty', () => {
expect(isContentLoaded(createState({ content: '' }))).toBe(false);
it('returns false when originalContent is empty', () => {
expect(isContentLoaded(createState({ originalContent: '' }))).toBe(false);
});
});
describe('contentChanged', () => {
it('returns true when content and originalContent are different', () => {
const state = createState({ content, originalContent: 'something else' });
expect(contentChanged(state)).toBe(true);
});
it('returns false when content and originalContent are the same', () => {
const state = createState({ content, originalContent: content });
expect(contentChanged(state)).toBe(false);
});
});
});
......@@ -35,8 +35,9 @@ describe('Static Site Editor Store mutations', () => {
expect(state.title).toBe(payload.title);
});
it('sets content', () => {
it('sets originalContent and content', () => {
expect(state.content).toBe(payload.content);
expect(state.originalContent).toBe(payload.content);
});
});
......@@ -49,4 +50,12 @@ describe('Static Site Editor Store mutations', () => {
expect(state.isLoadingContent).toBe(false);
});
});
describe('setContent', () => {
it('sets content', () => {
mutations[types.SET_CONTENT](state, content);
expect(state.content).toBe(content);
});
});
});
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