Commit 187ee320 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent a7dc052b
...@@ -53,11 +53,19 @@ export default { ...@@ -53,11 +53,19 @@ export default {
/> />
<ci-icon v-else :status="iconStatus" :size="24" /> <ci-icon v-else :status="iconStatus" :size="24" />
</div> </div>
<div class="report-block-list-issue-description"> <div class="report-block-list-issue-description">
<div class="report-block-list-issue-description-text">{{ summary }}</div> <div class="report-block-list-issue-description-text">
{{ summary
<popover v-if="popoverOptions" :options="popoverOptions" /> }}<span v-if="popoverOptions" class="text-nowrap"
>&nbsp;<popover v-if="popoverOptions" :options="popoverOptions" class="align-top" />
</span>
</div>
</div>
<div
v-if="$slots.default"
class="text-right flex-fill d-flex justify-content-end flex-column flex-sm-row"
>
<slot></slot>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { GlNewButton } from '@gitlab/ui'; import { GlNewButton, GlLoadingIcon } from '@gitlab/ui';
export default { export default {
components: { components: {
GlNewButton, GlNewButton,
GlLoadingIcon,
}, },
props: { props: {
saveable: { saveable: {
...@@ -11,12 +12,22 @@ export default { ...@@ -11,12 +12,22 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
savingChanges: {
type: Boolean,
required: false,
default: false,
},
}, },
}; };
</script> </script>
<template> <template>
<div class="d-flex bg-light border-top justify-content-between align-items-center py-3 px-4"> <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"> <gl-loading-icon :class="{ invisible: !savingChanges }" size="md" />
<gl-new-button
variant="success"
:disabled="!saveable || savingChanges"
@click="$emit('submit')"
>
{{ __('Submit Changes') }} {{ __('Submit Changes') }}
</gl-new-button> </gl-new-button>
</div> </div>
......
...@@ -12,14 +12,14 @@ export default { ...@@ -12,14 +12,14 @@ export default {
Toolbar, Toolbar,
}, },
computed: { computed: {
...mapState(['content', 'isLoadingContent']), ...mapState(['content', 'isLoadingContent', 'isSavingChanges']),
...mapGetters(['isContentLoaded', 'contentChanged']), ...mapGetters(['isContentLoaded', 'contentChanged']),
}, },
mounted() { mounted() {
this.loadContent(); this.loadContent();
}, },
methods: { methods: {
...mapActions(['loadContent', 'setContent']), ...mapActions(['loadContent', 'setContent', 'submitChanges']),
}, },
}; };
</script> </script>
...@@ -41,7 +41,11 @@ export default { ...@@ -41,7 +41,11 @@ export default {
:value="content" :value="content"
@input="setContent" @input="setContent"
/> />
<toolbar :saveable="contentChanged" /> <toolbar
:saveable="contentChanged"
:saving-changes="isSavingChanges"
@submit="submitChanges"
/>
</div> </div>
</div> </div>
</template> </template>
...@@ -6,7 +6,7 @@ const initStaticSiteEditor = el => { ...@@ -6,7 +6,7 @@ const initStaticSiteEditor = el => {
const { projectId, path: sourcePath } = el.dataset; const { projectId, path: sourcePath } = el.dataset;
const store = createStore({ const store = createStore({
initialState: { projectId, sourcePath }, initialState: { projectId, sourcePath, username: window.gon.current_username },
}); });
return new Vue({ return new Vue({
......
// TODO implement
const submitContentChanges = () => new Promise(resolve => setTimeout(resolve, 1000));
export default submitContentChanges;
...@@ -3,6 +3,7 @@ import { __ } from '~/locale'; ...@@ -3,6 +3,7 @@ import { __ } from '~/locale';
import * as mutationTypes from './mutation_types'; import * as mutationTypes from './mutation_types';
import loadSourceContent from '~/static_site_editor/services/load_source_content'; import loadSourceContent from '~/static_site_editor/services/load_source_content';
import submitContentChanges from '~/static_site_editor/services/submit_content_changes';
export const loadContent = ({ commit, state: { sourcePath, projectId } }) => { export const loadContent = ({ commit, state: { sourcePath, projectId } }) => {
commit(mutationTypes.LOAD_CONTENT); commit(mutationTypes.LOAD_CONTENT);
...@@ -19,4 +20,15 @@ export const setContent = ({ commit }, content) => { ...@@ -19,4 +20,15 @@ export const setContent = ({ commit }, content) => {
commit(mutationTypes.SET_CONTENT, content); commit(mutationTypes.SET_CONTENT, content);
}; };
export const submitChanges = ({ state: { projectId, content, sourcePath, username }, commit }) => {
commit(mutationTypes.SUBMIT_CHANGES);
return submitContentChanges({ content, projectId, sourcePath, username })
.then(data => commit(mutationTypes.SUBMIT_CHANGES_SUCCESS, data))
.catch(error => {
commit(mutationTypes.SUBMIT_CHANGES_ERROR);
createFlash(error.message);
});
};
export default () => {}; export default () => {};
...@@ -2,3 +2,6 @@ export const LOAD_CONTENT = 'loadContent'; ...@@ -2,3 +2,6 @@ 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'; export const SET_CONTENT = 'setContent';
export const SUBMIT_CHANGES = 'submitChanges';
export const SUBMIT_CHANGES_SUCCESS = 'submitChangesSuccess';
export const SUBMIT_CHANGES_ERROR = 'submitChangesError';
...@@ -16,4 +16,15 @@ export default { ...@@ -16,4 +16,15 @@ export default {
[types.SET_CONTENT](state, content) { [types.SET_CONTENT](state, content) {
state.content = content; state.content = content;
}, },
[types.SUBMIT_CHANGES](state) {
state.isSavingChanges = true;
},
[types.SUBMIT_CHANGES_SUCCESS](state, meta) {
state.savedContentMeta = meta;
state.isSavingChanges = false;
state.originalContent = state.content;
},
[types.SUBMIT_CHANGES_ERROR](state) {
state.isSavingChanges = false;
},
}; };
const createState = (initialState = {}) => ({ const createState = (initialState = {}) => ({
username: null,
projectId: null, projectId: null,
sourcePath: null, sourcePath: null,
......
---
title: Update Auto DevOps docker version to 19.03.8
merge_request: 29081
author:
type: changed
...@@ -238,7 +238,7 @@ The following documentation relates to the DevOps **Verify** stage: ...@@ -238,7 +238,7 @@ The following documentation relates to the DevOps **Verify** stage:
| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. | | [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. |
| [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. | | [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. |
| [Multi-project pipelines](ci/multi_project_pipelines.md) **(PREMIUM)** | Visualize entire pipelines that span multiple projects, including all cross-project inter-dependencies. | | [Multi-project pipelines](ci/multi_project_pipelines.md) **(PREMIUM)** | Visualize entire pipelines that span multiple projects, including all cross-project inter-dependencies. |
| [Pipeline Graphs](ci/pipelines/index.md#visualizing-pipelines) | Visualize builds. | | [Pipeline Graphs](ci/pipelines/index.md#visualize-pipelines) | Visualize builds. |
| [Review Apps](ci/review_apps/index.md) | Preview changes to your application right from a merge request. | | [Review Apps](ci/review_apps/index.md) | Preview changes to your application right from a merge request. |
<div align="right"> <div align="right">
......
...@@ -240,6 +240,10 @@ Example response: ...@@ -240,6 +240,10 @@ Example response:
] ]
``` ```
NOTE: **Note:**
To distinguish between a project in the group and a project shared to the group, the `namespace` attribute can be used. When a project has been shared to the group, its `namespace` will be different from the group the request is being made for.
## Details of a group ## Details of a group
Get all details of a group. This endpoint can be accessed without authentication Get all details of a group. This endpoint can be accessed without authentication
...@@ -255,7 +259,7 @@ Parameters: ...@@ -255,7 +259,7 @@ Parameters:
| ------------------------ | -------------- | -------- | ----------- | | ------------------------ | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only). | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only). |
| `with_projects` | boolean | no | Include details from projects that belong to the specified group (defaults to `true`). | | `with_projects` | boolean | no | Include details from projects that belong to the specified group (defaults to `true`). (Deprecated, [will be removed in 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213797). To get the details of all projects within a group, use the [list a group's projects endpoint](#list-a-groups-projects).) |
```shell ```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4 curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4
...@@ -578,6 +582,10 @@ This endpoint returns: ...@@ -578,6 +582,10 @@ This endpoint returns:
and later. To get the details of all projects within a group, use the and later. To get the details of all projects within a group, use the
[list a group's projects endpoint](#list-a-groups-projects) instead. [list a group's projects endpoint](#list-a-groups-projects) instead.
NOTE: **Note:**
The `projects` and `shared_projects` attributes [will be deprecated in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213797). To get the details of all projects within a group, use the [list a group's projects endpoint](#list-a-groups-projects) instead.
Example response: Example response:
```json ```json
......
...@@ -50,7 +50,7 @@ There are some high level differences between the products worth mentioning: ...@@ -50,7 +50,7 @@ There are some high level differences between the products worth mentioning:
- on push - on push
- on [schedule](../pipelines/schedules.md) - on [schedule](../pipelines/schedules.md)
- from the [GitLab UI](../pipelines/index.md#manually-executing-pipelines) - from the [GitLab UI](../pipelines/index.md#run-a-pipeline-manually)
- by [API call](../triggers/README.md) - by [API call](../triggers/README.md)
- by [webhook](../triggers/README.md#triggering-a-pipeline-from-a-webhook) - by [webhook](../triggers/README.md#triggering-a-pipeline-from-a-webhook)
- by [ChatOps](../chatops/README.md) - by [ChatOps](../chatops/README.md)
......
...@@ -116,7 +116,7 @@ unexpected timing. For example, when a source or target branch is advanced. ...@@ -116,7 +116,7 @@ unexpected timing. For example, when a source or target branch is advanced.
In this case, the pipeline fails because of `fatal: reference is not a tree:` error, In this case, the pipeline fails because of `fatal: reference is not a tree:` error,
which indicates that the checkout-SHA is not found in the merge ref. which indicates that the checkout-SHA is not found in the merge ref.
This behavior was improved at GitLab 12.4 by introducing [Persistent pipeline refs](../../pipelines/index.md#persistent-pipeline-refs). This behavior was improved at GitLab 12.4 by introducing [Persistent pipeline refs](../../pipelines/index.md#troubleshooting-fatal-reference-is-not-a-tree).
You should be able to create pipelines at any timings without concerning the error. You should be able to create pipelines at any timings without concerning the error.
## Using Merge Trains **(PREMIUM)** ## Using Merge Trains **(PREMIUM)**
......
...@@ -38,7 +38,7 @@ With Multi-Project Pipelines you can visualize the entire pipeline, including al ...@@ -38,7 +38,7 @@ With Multi-Project Pipelines you can visualize the entire pipeline, including al
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/2121) in [GitLab Premium 9.3](https://about.gitlab.com/releases/2017/06/22/gitlab-9-3-released/#multi-project-pipeline-graphs). > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/2121) in [GitLab Premium 9.3](https://about.gitlab.com/releases/2017/06/22/gitlab-9-3-released/#multi-project-pipeline-graphs).
When you configure GitLab CI/CD for your project, you can visualize the stages of your When you configure GitLab CI/CD for your project, you can visualize the stages of your
[jobs](pipelines/index.md#configuring-pipelines) on a [pipeline graph](pipelines/index.md#visualizing-pipelines). [jobs](pipelines/index.md#configure-a-pipeline) on a [pipeline graph](pipelines/index.md#visualize-pipelines).
![Multi-project pipeline graph](img/multi_project_pipeline_graph.png) ![Multi-project pipeline graph](img/multi_project_pipeline_graph.png)
......
This diff is collapsed.
...@@ -465,7 +465,7 @@ limitations with the current Auto DevOps scripting environment. ...@@ -465,7 +465,7 @@ limitations with the current Auto DevOps scripting environment.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/44059) in GitLab 10.8. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/44059) in GitLab 10.8.
[Manually triggered pipelines](../pipelines/index.md#manually-executing-pipelines) allow you to override the value of a current variable. [Manually triggered pipelines](../pipelines/index.md#run-a-pipeline-manually) allow you to override the value of a current variable.
For instance, suppose you added a For instance, suppose you added a
[custom variable `$TEST`](#creating-a-custom-environment-variable) [custom variable `$TEST`](#creating-a-custom-environment-variable)
...@@ -621,7 +621,7 @@ variables that were set, etc. ...@@ -621,7 +621,7 @@ variables that were set, etc.
Before enabling this, you should ensure jobs are visible to Before enabling this, you should ensure jobs are visible to
[team members only](../../user/permissions.md#project-features). You should [team members only](../../user/permissions.md#project-features). You should
also [erase](../pipelines/index.md#accessing-individual-jobs) all generated job logs also [erase](../pipelines/index.md#view-jobs-in-a-pipeline) all generated job logs
before making them visible again. before making them visible again.
To enable debug logs (traces), set the `CI_DEBUG_TRACE` variable to `true`: To enable debug logs (traces), set the `CI_DEBUG_TRACE` variable to `true`:
......
...@@ -521,7 +521,7 @@ increasing the rollout up to 100%. ...@@ -521,7 +521,7 @@ increasing the rollout up to 100%.
If `INCREMENTAL_ROLLOUT_MODE` is set to `manual` in your project, then instead If `INCREMENTAL_ROLLOUT_MODE` is set to `manual` in your project, then instead
of the standard `production` job, 4 different of the standard `production` job, 4 different
[manual jobs](../../ci/pipelines/index.md#manual-actions-from-pipeline-graphs) [manual jobs](../../ci/pipelines/index.md#add-manual-interaction-to-your-pipeline)
will be created: will be created:
1. `rollout 10%` 1. `rollout 10%`
......
...@@ -425,7 +425,7 @@ read through the documentation on the [new CI/CD permissions model](project/new_ ...@@ -425,7 +425,7 @@ read through the documentation on the [new CI/CD permissions model](project/new_
The permission to merge or push to protected branches is used to define if a user can The permission to merge or push to protected branches is used to define if a user can
run CI/CD pipelines and execute actions on jobs that are related to those branches. run CI/CD pipelines and execute actions on jobs that are related to those branches.
See [Security on protected branches](../ci/pipelines/index.md#security-on-protected-branches) See [Security on protected branches](../ci/pipelines/index.md#pipeline-security-on-protected-branches)
for details about the pipelines security model. for details about the pipelines security model.
## LDAP users permissions ## LDAP users permissions
......
...@@ -70,7 +70,7 @@ When you create a project in GitLab, you'll have access to a large number of ...@@ -70,7 +70,7 @@ When you create a project in GitLab, you'll have access to a large number of
your GitLab CI/CD pipelines from the UI your GitLab CI/CD pipelines from the UI
- [Scheduled Pipelines](../../ci/pipelines/schedules.md): Schedule a pipeline - [Scheduled Pipelines](../../ci/pipelines/schedules.md): Schedule a pipeline
to start at a chosen time to start at a chosen time
- [Pipeline Graphs](../../ci/pipelines/index.md#visualizing-pipelines): View your - [Pipeline Graphs](../../ci/pipelines/index.md#visualize-pipelines): View your
entire pipeline from the UI entire pipeline from the UI
- [Job artifacts](../../ci/pipelines/job_artifacts.md): Define, - [Job artifacts](../../ci/pipelines/job_artifacts.md): Define,
browse, and download job artifacts browse, and download job artifacts
......
...@@ -101,7 +101,7 @@ or link to useful information directly in the merge request page: ...@@ -101,7 +101,7 @@ or link to useful information directly in the merge request page:
| [Metrics Reports](../../../ci/metrics_reports.md) **(PREMIUM)** | Display the Metrics Report on the merge request so that it's fast and easy to identify changes to important metrics. | | [Metrics Reports](../../../ci/metrics_reports.md) **(PREMIUM)** | Display the Metrics Report on the merge request so that it's fast and easy to identify changes to important metrics. |
| [Multi-Project pipelines](../../../ci/multi_project_pipelines.md) **(PREMIUM)** | When you set up GitLab CI/CD across multiple projects, you can visualize the entire pipeline, including all cross-project interdependencies. | | [Multi-Project pipelines](../../../ci/multi_project_pipelines.md) **(PREMIUM)** | When you set up GitLab CI/CD across multiple projects, you can visualize the entire pipeline, including all cross-project interdependencies. |
| [Pipelines for merge requests](../../../ci/merge_request_pipelines/index.md) | Customize a specific pipeline structure for merge requests in order to speed the cycle up by running only important jobs. | | [Pipelines for merge requests](../../../ci/merge_request_pipelines/index.md) | Customize a specific pipeline structure for merge requests in order to speed the cycle up by running only important jobs. |
| [Pipeline Graphs](../../../ci/pipelines/index.md#visualizing-pipelines) | View the status of pipelines within the merge request, including the deployment process. | | [Pipeline Graphs](../../../ci/pipelines/index.md#visualize-pipelines) | View the status of pipelines within the merge request, including the deployment process. |
| [Test Coverage visualization](test_coverage_visualization.md) | See test coverage results for merge requests, within the file diff. | | [Test Coverage visualization](test_coverage_visualization.md) | See test coverage results for merge requests, within the file diff. |
### Security Reports **(ULTIMATE)** ### Security Reports **(ULTIMATE)**
......
...@@ -187,7 +187,7 @@ Additionally, direct pushes to the protected branch are denied if a rule is matc ...@@ -187,7 +187,7 @@ Additionally, direct pushes to the protected branch are denied if a rule is matc
The permission to merge or push to protected branches is used to define if a user can The permission to merge or push to protected branches is used to define if a user can
run CI/CD pipelines and execute actions on jobs that are related to those branches. run CI/CD pipelines and execute actions on jobs that are related to those branches.
See [Security on protected branches](../../ci/pipelines/index.md#security-on-protected-branches) See [Security on protected branches](../../ci/pipelines/index.md#pipeline-security-on-protected-branches)
for details about the pipelines security model. for details about the pipelines security model.
## Changelog ## Changelog
......
performance: performance:
stage: performance stage: performance
image: docker:19.03.5 image: docker:19.03.8
allow_failure: true allow_failure: true
variables: variables:
DOCKER_TLS_CERTDIR: "" DOCKER_TLS_CERTDIR: ""
services: services:
- docker:19.03.5-dind - docker:19.03.8-dind
script: script:
- | - |
if ! docker info &>/dev/null; then if ! docker info &>/dev/null; then
......
build: build:
stage: build stage: build
image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image:v0.2.0" image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image:v0.2.1"
variables: variables:
DOCKER_TLS_CERTDIR: "" DOCKER_TLS_CERTDIR: ""
services: services:
- docker:19.03.5-dind - docker:19.03.8-dind
script: script:
- | - |
if [[ -z "$CI_COMMIT_TAG" ]]; then if [[ -z "$CI_COMMIT_TAG" ]]; then
......
code_quality: code_quality:
stage: test stage: test
image: docker:19.03.5 image: docker:19.03.8
allow_failure: true allow_failure: true
services: services:
- docker:19.03.5-dind - docker:19.03.8-dind
variables: variables:
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "" DOCKER_TLS_CERTDIR: ""
......
...@@ -71,6 +71,11 @@ msgstr "" ...@@ -71,6 +71,11 @@ msgstr ""
msgid "\"%{path}\" did not exist on \"%{ref}\"" msgid "\"%{path}\" did not exist on \"%{ref}\""
msgstr "" msgstr ""
msgid "%d URL scanned"
msgid_plural "%d URLs scanned"
msgstr[0] ""
msgstr[1] ""
msgid "%d changed file" msgid "%d changed file"
msgid_plural "%d changed files" msgid_plural "%d changed files"
msgstr[0] "" msgstr[0] ""
...@@ -22624,6 +22629,9 @@ msgstr "" ...@@ -22624,6 +22629,9 @@ msgstr ""
msgid "View deployment" msgid "View deployment"
msgstr "" msgstr ""
msgid "View details"
msgstr ""
msgid "View details: %{details_url}" msgid "View details: %{details_url}"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlNewButton } from '@gitlab/ui'; import { GlNewButton, GlLoadingIcon } from '@gitlab/ui';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
...@@ -16,6 +16,7 @@ describe('Static Site Editor Toolbar', () => { ...@@ -16,6 +16,7 @@ describe('Static Site Editor Toolbar', () => {
}; };
const findSaveChangesButton = () => wrapper.find(GlNewButton); const findSaveChangesButton = () => wrapper.find(GlNewButton);
const findLoadingIndicator = () => wrapper.find(GlLoadingIcon);
beforeEach(() => { beforeEach(() => {
buildWrapper(); buildWrapper();
...@@ -33,6 +34,10 @@ describe('Static Site Editor Toolbar', () => { ...@@ -33,6 +34,10 @@ describe('Static Site Editor Toolbar', () => {
expect(findSaveChangesButton().attributes('disabled')).toBe('true'); expect(findSaveChangesButton().attributes('disabled')).toBe('true');
}); });
it('does not display saving changes indicator', () => {
expect(findLoadingIndicator().classes()).toContain('invisible');
});
describe('when saveable', () => { describe('when saveable', () => {
it('enables Submit Changes button', () => { it('enables Submit Changes button', () => {
buildWrapper({ saveable: true }); buildWrapper({ saveable: true });
...@@ -40,4 +45,26 @@ describe('Static Site Editor Toolbar', () => { ...@@ -40,4 +45,26 @@ describe('Static Site Editor Toolbar', () => {
expect(findSaveChangesButton().attributes('disabled')).toBeFalsy(); expect(findSaveChangesButton().attributes('disabled')).toBeFalsy();
}); });
}); });
describe('when saving changes', () => {
beforeEach(() => {
buildWrapper({ saveable: true, savingChanges: true });
});
it('disables Submit Changes button', () => {
expect(findSaveChangesButton().attributes('disabled')).toBe('true');
});
it('displays saving changes indicator', () => {
expect(findLoadingIndicator().classes()).not.toContain('invisible');
});
});
it('emits submit event when submit button is clicked', () => {
buildWrapper({ saveable: true });
findSaveChangesButton().vm.$emit('click');
expect(wrapper.emitted('submit')).toHaveLength(1);
});
}); });
...@@ -9,6 +9,8 @@ import StaticSiteEditor from '~/static_site_editor/components/static_site_editor ...@@ -9,6 +9,8 @@ import StaticSiteEditor from '~/static_site_editor/components/static_site_editor
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 { sourceContent } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -18,10 +20,12 @@ describe('StaticSiteEditor', () => { ...@@ -18,10 +20,12 @@ describe('StaticSiteEditor', () => {
let store; let store;
let loadContentActionMock; let loadContentActionMock;
let setContentActionMock; let setContentActionMock;
let submitChangesActionMock;
const buildStore = ({ initialState, getters } = {}) => { const buildStore = ({ initialState, getters } = {}) => {
loadContentActionMock = jest.fn(); loadContentActionMock = jest.fn();
setContentActionMock = jest.fn(); setContentActionMock = jest.fn();
submitChangesActionMock = jest.fn();
store = new Vuex.Store({ store = new Vuex.Store({
state: createState(initialState), state: createState(initialState),
...@@ -33,6 +37,7 @@ describe('StaticSiteEditor', () => { ...@@ -33,6 +37,7 @@ describe('StaticSiteEditor', () => {
actions: { actions: {
loadContent: loadContentActionMock, loadContent: loadContentActionMock,
setContent: setContentActionMock, setContent: setContentActionMock,
submitChanges: submitChangesActionMock,
}, },
}); });
}; };
...@@ -119,18 +124,35 @@ describe('StaticSiteEditor', () => { ...@@ -119,18 +124,35 @@ describe('StaticSiteEditor', () => {
expect(findSkeletonLoader().exists()).toBe(true); expect(findSkeletonLoader().exists()).toBe(true);
}); });
it('sets toolbar as saving when saving changes', () => {
buildContentLoadedStore({
initialState: {
isSavingChanges: true,
},
});
buildWrapper();
expect(findPublishToolbar().props('savingChanges')).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', () => { it('dispatches setContent action when edit area emits input event', () => {
const content = 'new content';
buildContentLoadedStore(); buildContentLoadedStore();
buildWrapper(); buildWrapper();
findEditArea().vm.$emit('input', content); findEditArea().vm.$emit('input', sourceContent);
expect(setContentActionMock).toHaveBeenCalledWith(expect.anything(), sourceContent, undefined);
});
it('dispatches submitChanges action when toolbar emits submit event', () => {
buildContentLoadedStore();
buildWrapper();
findPublishToolbar().vm.$emit('submit');
expect(setContentActionMock).toHaveBeenCalledWith(expect.anything(), content, undefined); expect(submitChangesActionMock).toHaveBeenCalled();
}); });
}); });
...@@ -14,5 +14,23 @@ twitter_image: '/images/tweets/handbook-gitlab.png' ...@@ -14,5 +14,23 @@ twitter_image: '/images/tweets/handbook-gitlab.png'
export const sourceContentTitle = 'Handbook'; export const sourceContentTitle = 'Handbook';
export const username = 'gitlabuser';
export const projectId = '123456'; export const projectId = '123456';
export const sourcePath = 'foobar.md.html'; export const sourcePath = 'foobar.md.html';
export const savedContentMeta = {
branch: {
label: 'foobar',
url: 'foobar/-/tree/foorbar',
},
commit: {
label: 'c1461b08 ',
url: 'foobar/-/c1461b08',
},
mergeRequest: {
label: '123',
url: 'foobar/-/merge_requests/123',
},
};
export const submitChangesError = 'Could not save changes';
...@@ -3,18 +3,23 @@ import createState from '~/static_site_editor/store/state'; ...@@ -3,18 +3,23 @@ import createState from '~/static_site_editor/store/state';
import * as actions from '~/static_site_editor/store/actions'; import * as actions from '~/static_site_editor/store/actions';
import * as mutationTypes from '~/static_site_editor/store/mutation_types'; import * as mutationTypes from '~/static_site_editor/store/mutation_types';
import loadSourceContent from '~/static_site_editor/services/load_source_content'; import loadSourceContent from '~/static_site_editor/services/load_source_content';
import submitContentChanges from '~/static_site_editor/services/submit_content_changes';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { import {
username,
projectId, projectId,
sourcePath, sourcePath,
sourceContentTitle as title, sourceContentTitle as title,
sourceContent as content, sourceContent as content,
savedContentMeta,
submitChangesError,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
jest.mock('~/static_site_editor/services/load_source_content', () => jest.fn()); jest.mock('~/static_site_editor/services/load_source_content', () => jest.fn());
jest.mock('~/static_site_editor/services/submit_content_changes', () => jest.fn());
describe('Static Site Editor Store actions', () => { describe('Static Site Editor Store actions', () => {
let state; let state;
...@@ -84,4 +89,59 @@ describe('Static Site Editor Store actions', () => { ...@@ -84,4 +89,59 @@ describe('Static Site Editor Store actions', () => {
]); ]);
}); });
}); });
describe('submitChanges', () => {
describe('on success', () => {
beforeEach(() => {
state = createState({
projectId,
content,
username,
sourcePath,
});
submitContentChanges.mockResolvedValueOnce(savedContentMeta);
});
it('commits submitChangesSuccess mutation', () => {
testAction(
actions.submitChanges,
null,
state,
[
{ type: mutationTypes.SUBMIT_CHANGES },
{ type: mutationTypes.SUBMIT_CHANGES_SUCCESS, payload: savedContentMeta },
],
[],
);
expect(submitContentChanges).toHaveBeenCalledWith({
username,
projectId,
content,
sourcePath,
});
});
});
describe('on error', () => {
const expectedMutations = [
{ type: mutationTypes.SUBMIT_CHANGES },
{ type: mutationTypes.SUBMIT_CHANGES_ERROR },
];
beforeEach(() => {
submitContentChanges.mockRejectedValueOnce(new Error(submitChangesError));
});
it('dispatches receiveContentError', () => {
testAction(actions.submitChanges, null, state, expectedMutations);
});
it('displays flash communicating error', () => {
return testAction(actions.submitChanges, null, state, expectedMutations).then(() => {
expect(createFlash).toHaveBeenCalledWith(submitChangesError);
});
});
});
});
}); });
import createState from '~/static_site_editor/store/state'; import createState from '~/static_site_editor/store/state';
import mutations from '~/static_site_editor/store/mutations'; import mutations from '~/static_site_editor/store/mutations';
import * as types from '~/static_site_editor/store/mutation_types'; import * as types from '~/static_site_editor/store/mutation_types';
import { sourceContentTitle as title, sourceContent as content } from '../mock_data'; import {
sourceContentTitle as title,
sourceContent as content,
savedContentMeta,
} from '../mock_data';
describe('Static Site Editor Store mutations', () => { describe('Static Site Editor Store mutations', () => {
let state; let state;
const contentLoadedPayload = { title, content };
beforeEach(() => { beforeEach(() => {
state = createState(); state = createState();
}); });
describe('loadContent', () => { it.each`
beforeEach(() => { mutation | stateProperty | payload | expectedValue
mutations[types.LOAD_CONTENT](state); ${types.LOAD_CONTENT} | ${'isLoadingContent'} | ${undefined} | ${true}
}); ${types.RECEIVE_CONTENT_SUCCESS} | ${'isLoadingContent'} | ${contentLoadedPayload} | ${false}
${types.RECEIVE_CONTENT_SUCCESS} | ${'title'} | ${contentLoadedPayload} | ${title}
it('sets isLoadingContent to true', () => { ${types.RECEIVE_CONTENT_SUCCESS} | ${'content'} | ${contentLoadedPayload} | ${content}
expect(state.isLoadingContent).toBe(true); ${types.RECEIVE_CONTENT_SUCCESS} | ${'originalContent'} | ${contentLoadedPayload} | ${content}
}); ${types.RECEIVE_CONTENT_ERROR} | ${'isLoadingContent'} | ${undefined} | ${false}
}); ${types.SET_CONTENT} | ${'content'} | ${content} | ${content}
${types.SUBMIT_CHANGES} | ${'isSavingChanges'} | ${undefined} | ${true}
describe('receiveContentSuccess', () => { ${types.SUBMIT_CHANGES_SUCCESS} | ${'savedContentMeta'} | ${savedContentMeta} | ${savedContentMeta}
const payload = { title, content }; ${types.SUBMIT_CHANGES_SUCCESS} | ${'isSavingChanges'} | ${savedContentMeta} | ${false}
${types.SUBMIT_CHANGES_ERROR} | ${'isSavingChanges'} | ${undefined} | ${false}
beforeEach(() => { `(
mutations[types.RECEIVE_CONTENT_SUCCESS](state, payload); '$mutation sets $stateProperty to $expectedValue',
}); ({ mutation, stateProperty, payload, expectedValue }) => {
mutations[mutation](state, payload);
it('sets current state to LOADING', () => { expect(state[stateProperty]).toBe(expectedValue);
expect(state.isLoadingContent).toBe(false); },
}); );
it('sets title', () => { it(`${types.SUBMIT_CHANGES_SUCCESS} sets originalContent to content current value`, () => {
expect(state.title).toBe(payload.title); const editedContent = `${content} plus something else`;
});
state = createState({
it('sets originalContent and content', () => { originalContent: content,
expect(state.content).toBe(payload.content); content: editedContent,
expect(state.originalContent).toBe(payload.content); });
}); mutations[types.SUBMIT_CHANGES_SUCCESS](state);
});
expect(state.originalContent).toBe(state.content);
describe('receiveContentError', () => {
beforeEach(() => {
mutations[types.RECEIVE_CONTENT_ERROR](state);
});
it('sets current state to LOADING_ERROR', () => {
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