Commit 466be685 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '212561-sse-display-actions-toolbar' into 'master'

Static Site Editor Save Changes toolbar

See merge request gitlab-org/gitlab!28972
parents 72b176e9 a929c5f6
<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'; ...@@ -3,27 +3,29 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import { GlSkeletonLoader } from '@gitlab/ui'; import { GlSkeletonLoader } from '@gitlab/ui';
import EditArea from './edit_area.vue'; import EditArea from './edit_area.vue';
import Toolbar from './publish_toolbar.vue';
export default { export default {
components: { components: {
EditArea, EditArea,
GlSkeletonLoader, GlSkeletonLoader,
Toolbar,
}, },
computed: { computed: {
...mapState(['content', 'isLoadingContent']), ...mapState(['content', 'isLoadingContent']),
...mapGetters(['isContentLoaded']), ...mapGetters(['isContentLoaded', 'contentChanged']),
}, },
mounted() { mounted() {
this.loadContent(); this.loadContent();
}, },
methods: { methods: {
...mapActions(['loadContent']), ...mapActions(['loadContent', 'setContent']),
}, },
}; };
</script> </script>
<template> <template>
<div class="d-flex justify-content-center h-100"> <div class="d-flex justify-content-center h-100 pt-2">
<div v-if="isLoadingContent" class="w-50 h-50 mt-2"> <div v-if="isLoadingContent" class="w-50 h-50">
<gl-skeleton-loader :width="500" :height="102"> <gl-skeleton-loader :width="500" :height="102">
<rect width="500" height="16" rx="4" /> <rect width="500" height="16" rx="4" />
<rect y="20" width="375" height="16" rx="4" /> <rect y="20" width="375" height="16" rx="4" />
...@@ -33,6 +35,13 @@ export default { ...@@ -33,6 +35,13 @@ export default {
<rect x="410" y="40" width="90" height="16" rx="4" /> <rect x="410" y="40" width="90" height="16" rx="4" />
</gl-skeleton-loader> </gl-skeleton-loader>
</div> </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> </div>
</template> </template>
...@@ -15,4 +15,8 @@ export const loadContent = ({ commit, state: { sourcePath, projectId } }) => { ...@@ -15,4 +15,8 @@ export const loadContent = ({ commit, state: { sourcePath, projectId } }) => {
}); });
}; };
export const setContent = ({ commit }, content) => {
commit(mutationTypes.SET_CONTENT, content);
};
export default () => {}; export default () => {};
// eslint-disable-next-line import/prefer-default-export export const isContentLoaded = ({ originalContent }) => Boolean(originalContent);
export const isContentLoaded = ({ content }) => Boolean(content); export const contentChanged = ({ originalContent, content }) => originalContent !== content;
export const LOAD_CONTENT = 'loadContent'; export const LOAD_CONTENT = 'loadContent';
export const RECEIVE_CONTENT_SUCCESS = 'receiveContentSuccess'; export const RECEIVE_CONTENT_SUCCESS = 'receiveContentSuccess';
export const RECEIVE_CONTENT_ERROR = 'receiveContentError'; export const RECEIVE_CONTENT_ERROR = 'receiveContentError';
export const SET_CONTENT = 'setContent';
...@@ -8,8 +8,12 @@ export default { ...@@ -8,8 +8,12 @@ export default {
state.isLoadingContent = false; state.isLoadingContent = false;
state.title = title; state.title = title;
state.content = content; state.content = content;
state.originalContent = content;
}, },
[types.RECEIVE_CONTENT_ERROR](state) { [types.RECEIVE_CONTENT_ERROR](state) {
state.isLoadingContent = false; state.isLoadingContent = false;
}, },
[types.SET_CONTENT](state, content) {
state.content = content;
},
}; };
...@@ -3,7 +3,9 @@ const createState = (initialState = {}) => ({ ...@@ -3,7 +3,9 @@ const createState = (initialState = {}) => ({
sourcePath: null, sourcePath: null,
isLoadingContent: false, isLoadingContent: false,
isSavingChanges: false,
originalContent: '',
content: '', content: '',
title: '', title: '',
......
#static-site-editor{ data: {} } #static-site-editor{ data: { project_id: '8', path: 'README.md' } }
...@@ -19388,6 +19388,9 @@ msgstr "" ...@@ -19388,6 +19388,9 @@ msgstr ""
msgid "Subkeys" msgid "Subkeys"
msgstr "" msgstr ""
msgid "Submit Changes"
msgstr ""
msgid "Submit a review" msgid "Submit a review"
msgstr "" 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'; ...@@ -7,6 +7,7 @@ import createState from '~/static_site_editor/store/state';
import StaticSiteEditor from '~/static_site_editor/components/static_site_editor.vue'; import StaticSiteEditor from '~/static_site_editor/components/static_site_editor.vue';
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';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -16,18 +17,31 @@ describe('StaticSiteEditor', () => { ...@@ -16,18 +17,31 @@ describe('StaticSiteEditor', () => {
let wrapper; let wrapper;
let store; let store;
let loadContentActionMock; let loadContentActionMock;
let setContentActionMock;
const buildStore = ({ initialState, getters } = {}) => { const buildStore = ({ initialState, getters } = {}) => {
loadContentActionMock = jest.fn(); loadContentActionMock = jest.fn();
setContentActionMock = jest.fn();
store = new Vuex.Store({ store = new Vuex.Store({
state: createState(initialState), state: createState(initialState),
getters: { getters: {
isContentLoaded: () => false, isContentLoaded: () => false,
contentChanged: () => false,
...getters, ...getters,
}, },
actions: { actions: {
loadContent: loadContentActionMock, loadContent: loadContentActionMock,
setContent: setContentActionMock,
},
});
};
const buildContentLoadedStore = ({ initialState, getters } = {}) => {
buildStore({
initialState,
getters: {
isContentLoaded: () => true,
...getters,
}, },
}); });
}; };
...@@ -40,6 +54,8 @@ describe('StaticSiteEditor', () => { ...@@ -40,6 +54,8 @@ describe('StaticSiteEditor', () => {
}; };
const findEditArea = () => wrapper.find(EditArea); const findEditArea = () => wrapper.find(EditArea);
const findPublishToolbar = () => wrapper.find(PublishToolbar);
const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader);
beforeEach(() => { beforeEach(() => {
buildStore(); buildStore();
...@@ -54,6 +70,10 @@ describe('StaticSiteEditor', () => { ...@@ -54,6 +70,10 @@ describe('StaticSiteEditor', () => {
it('does not render edit area', () => { it('does not render edit area', () => {
expect(findEditArea().exists()).toBe(false); expect(findEditArea().exists()).toBe(false);
}); });
it('does not render toolbar', () => {
expect(findPublishToolbar().exists()).toBe(false);
});
}); });
describe('when content is loaded', () => { describe('when content is loaded', () => {
...@@ -68,19 +88,49 @@ describe('StaticSiteEditor', () => { ...@@ -68,19 +88,49 @@ describe('StaticSiteEditor', () => {
expect(findEditArea().exists()).toBe(true); expect(findEditArea().exists()).toBe(true);
}); });
it('does not render skeleton loader', () => {
expect(findSkeletonLoader().exists()).toBe(false);
});
it('passes page content to edit area', () => { it('passes page content to edit area', () => {
expect(findEditArea().props('value')).toBe(content); 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 } }); buildStore({ initialState: { isLoadingContent: true } });
buildWrapper(); buildWrapper();
expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true); expect(findSkeletonLoader().exists()).toBe(true);
}); });
it('dispatches load content action', () => { it('dispatches load content action', () => {
expect(loadContentActionMock).toHaveBeenCalled(); 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', () => { ...@@ -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 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'; import { sourceContent as content } from '../mock_data';
describe('Static Site Editor Store getters', () => { describe('Static Site Editor Store getters', () => {
describe('isContentLoaded', () => { describe('isContentLoaded', () => {
it('returns true when content is not empty', () => { it('returns true when originalContent is not empty', () => {
expect(isContentLoaded(createState({ content }))).toBe(true); expect(isContentLoaded(createState({ originalContent: content }))).toBe(true);
}); });
it('returns false when content is empty', () => { it('returns false when originalContent is empty', () => {
expect(isContentLoaded(createState({ content: '' }))).toBe(false); 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', () => { ...@@ -35,8 +35,9 @@ describe('Static Site Editor Store mutations', () => {
expect(state.title).toBe(payload.title); expect(state.title).toBe(payload.title);
}); });
it('sets content', () => { it('sets originalContent and content', () => {
expect(state.content).toBe(payload.content); expect(state.content).toBe(payload.content);
expect(state.originalContent).toBe(payload.content);
}); });
}); });
...@@ -49,4 +50,12 @@ describe('Static Site Editor Store mutations', () => { ...@@ -49,4 +50,12 @@ describe('Static Site Editor Store mutations', () => {
expect(state.isLoadingContent).toBe(false); 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