Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e4df6a7c53
commit
420215876f
22 changed files with 187 additions and 134 deletions
|
@ -21,6 +21,7 @@ export default {
|
|||
name: this.environment.name,
|
||||
externalUrl: this.environment.external_url,
|
||||
},
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -28,6 +29,7 @@ export default {
|
|||
this.formEnvironment = environment;
|
||||
},
|
||||
onSubmit() {
|
||||
this.loading = true;
|
||||
axios
|
||||
.put(this.updateEnvironmentPath, {
|
||||
id: this.environment.id,
|
||||
|
@ -38,6 +40,7 @@ export default {
|
|||
.catch((error) => {
|
||||
const message = error.response.data.message[0];
|
||||
createFlash({ message });
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -48,6 +51,7 @@ export default {
|
|||
:cancel-path="projectEnvironmentsPath"
|
||||
:environment="formEnvironment"
|
||||
:title="__('Edit environment')"
|
||||
:loading="loading"
|
||||
@change="onChange"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
|
|
|
@ -26,6 +26,11 @@ export default {
|
|||
required: true,
|
||||
type: String,
|
||||
},
|
||||
loading: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
header: __('Environments'),
|
||||
|
@ -42,21 +47,26 @@ export default {
|
|||
helpPagePath: helpPagePath('ci/environments/index.md'),
|
||||
data() {
|
||||
return {
|
||||
errors: {
|
||||
visited: {
|
||||
name: null,
|
||||
url: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
valid() {
|
||||
return {
|
||||
name: this.visited.name && this.environment.name !== '',
|
||||
url: this.visited.url && isAbsolute(this.environment.externalUrl),
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(env) {
|
||||
this.$emit('change', env);
|
||||
},
|
||||
validateUrl() {
|
||||
this.errors.url = isAbsolute(this.environment.externalUrl);
|
||||
},
|
||||
validateName() {
|
||||
this.errors.name = this.environment.name !== '';
|
||||
visit(field) {
|
||||
this.visited[field] = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -89,40 +99,45 @@ export default {
|
|||
<gl-form-group
|
||||
:label="$options.i18n.nameLabel"
|
||||
label-for="environment_name"
|
||||
:state="errors.name"
|
||||
:state="valid.name"
|
||||
:invalid-feedback="$options.i18n.nameFeedback"
|
||||
>
|
||||
<gl-form-input
|
||||
id="environment_name"
|
||||
:value="environment.name"
|
||||
:state="errors.name"
|
||||
:state="valid.name"
|
||||
name="environment[name]"
|
||||
required
|
||||
@input="onChange({ ...environment, name: $event })"
|
||||
@blur="validateName"
|
||||
@blur="visit('name')"
|
||||
/>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
:label="$options.i18n.urlLabel"
|
||||
:state="errors.url"
|
||||
:state="valid.url"
|
||||
:invalid-feedback="$options.i18n.urlFeedback"
|
||||
label-for="environment_external_url"
|
||||
>
|
||||
<gl-form-input
|
||||
id="environment_external_url"
|
||||
:value="environment.externalUrl"
|
||||
:state="errors.url"
|
||||
:state="valid.url"
|
||||
name="environment[external_url]"
|
||||
type="url"
|
||||
@input="onChange({ ...environment, externalUrl: $event })"
|
||||
@blur="validateUrl"
|
||||
@blur="visit('url')"
|
||||
/>
|
||||
</gl-form-group>
|
||||
|
||||
<div class="form-actions">
|
||||
<gl-button type="submit" variant="confirm" name="commit" class="js-no-auto-disable">{{
|
||||
$options.i18n.save
|
||||
}}</gl-button>
|
||||
<gl-button
|
||||
:loading="loading"
|
||||
type="submit"
|
||||
variant="confirm"
|
||||
name="commit"
|
||||
class="js-no-auto-disable"
|
||||
>{{ $options.i18n.save }}</gl-button
|
||||
>
|
||||
<gl-button :href="cancelPath">{{ $options.i18n.cancel }}</gl-button>
|
||||
</div>
|
||||
</gl-form>
|
||||
|
|
|
@ -15,6 +15,7 @@ export default {
|
|||
name: '',
|
||||
externalUrl: '',
|
||||
},
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -22,6 +23,7 @@ export default {
|
|||
this.environment = env;
|
||||
},
|
||||
onSubmit() {
|
||||
this.loading = true;
|
||||
axios
|
||||
.post(this.projectEnvironmentsPath, {
|
||||
name: this.environment.name,
|
||||
|
@ -31,6 +33,7 @@ export default {
|
|||
.catch((error) => {
|
||||
const message = error.response.data.message[0];
|
||||
createFlash({ message });
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -41,6 +44,7 @@ export default {
|
|||
:cancel-path="projectEnvironmentsPath"
|
||||
:environment="environment"
|
||||
:title="__('New environment')"
|
||||
:loading="loading"
|
||||
@change="onChange($event)"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
|
|
|
@ -83,7 +83,11 @@ export default {
|
|||
<div class="gl-display-flex gl-align-items-baseline">
|
||||
<h3 class="gl-font-lg gl-m-0 gl-mr-3">{{ feature.name }}</h3>
|
||||
|
||||
<div :class="statusClasses" data-testid="feature-status">
|
||||
<div
|
||||
:class="statusClasses"
|
||||
data-testid="feature-status"
|
||||
:data-qa-selector="`${feature.type}_status`"
|
||||
>
|
||||
<template v-if="hasStatus">
|
||||
<template v-if="enabled">
|
||||
<gl-icon name="check-circle-filled" />
|
||||
|
@ -112,6 +116,7 @@ export default {
|
|||
:href="feature.configurationPath"
|
||||
variant="confirm"
|
||||
:category="configurationButton.category"
|
||||
:data-qa-selector="`${feature.type}_enable_button`"
|
||||
class="gl-mt-5"
|
||||
>
|
||||
{{ configurationButton.text }}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
.row.gl-mt-3.gl-mb-3
|
||||
.col-lg-3
|
||||
%h4.gl-mt-0
|
||||
= _("Environments")
|
||||
%p
|
||||
- link_to_read_more = link_to(_("More information"), help_page_path("ci/environments/index.md"))
|
||||
= _("Environments allow you to track deployments of your application %{link_to_read_more}.").html_safe % { link_to_read_more: link_to_read_more }
|
||||
|
||||
= form_for [@project, @environment], html: { class: 'col-lg-9' } do |f|
|
||||
= form_errors(@environment)
|
||||
|
||||
.form-group
|
||||
= f.label :name, _('Name'), class: 'label-bold'
|
||||
= f.text_field :name, required: true, class: 'form-control'
|
||||
.form-group
|
||||
= f.label :external_url, _('External URL'), class: 'label-bold'
|
||||
= f.url_field :external_url, class: 'form-control'
|
||||
|
||||
.form-actions
|
||||
= f.submit _('Save'), class: 'gl-button btn btn-confirm'
|
||||
= link_to _('Cancel'), project_environments_path(@project), class: 'gl-button btn btn-cancel'
|
|
@ -334,8 +334,8 @@ end
|
|||
The iteration uses the `id` column of the `projects` table. The batching does not affect the
|
||||
subquery. This means for each iteration, the subquery is executed by the database. This adds a
|
||||
constant "load" on the query which often ends up in statement timeouts. We have an unknown number
|
||||
of confidential issues, the execution time and the accessed database rows depend on the data
|
||||
distribution in the `issues` table.
|
||||
of [confidential issues](../user/project/issues/confidential_issues.md), the execution time
|
||||
and the accessed database rows depend on the data distribution in the `issues` table.
|
||||
|
||||
NOTE:
|
||||
Using subqueries works only when the subquery returns a small number of rows.
|
||||
|
|
|
@ -86,7 +86,8 @@ is calculated properly.
|
|||
|
||||
### Confidential issues
|
||||
|
||||
Confidential issues can be accessed only by project members who are at least
|
||||
[Confidential issues](../user/project/issues/confidential_issues.md) can be accessed
|
||||
only by project members who are at least
|
||||
reporters (they can't be accessed by guests). Additionally they can be accessed
|
||||
by their authors and assignees.
|
||||
|
||||
|
|
|
@ -152,7 +152,8 @@ a resource that some subsequent readers should not be able to see.
|
|||
|
||||
For example, you might create an issue, and refer to a confidential issue `#1234`,
|
||||
which you have access to. This is rendered in the cached HTML as a link to
|
||||
that confidential issue, with data attributes containing its ID, the ID of the
|
||||
that [confidential issue](../user/project/issues/confidential_issues.md),
|
||||
with data attributes containing its ID, the ID of the
|
||||
project and other confidential data. A later reader, who has access to your issue
|
||||
might not have permission to read issue `#1234`, and so we need to redact
|
||||
these sensitive pieces of data. This is what `ReferenceParser` classes do.
|
||||
|
|
|
@ -124,7 +124,7 @@ To publish an incident:
|
|||
1. Create an issue in the project you enabled the GitLab Status Page settings in.
|
||||
1. A [project or group owner](../../user/permissions.md) must use the
|
||||
`/publish` [quick action](../../user/project/quick_actions.md) to publish the
|
||||
issue to the GitLab Status Page. Confidential issues can't be published.
|
||||
issue to the GitLab Status Page. [Confidential issues](../../user/project/issues/confidential_issues.md) can't be published.
|
||||
|
||||
A background worker publishes the issue onto the Status Page using the credentials
|
||||
you provided during setup. As part of publication, GitLab:
|
||||
|
@ -168,5 +168,6 @@ To change the incident status from `open` to `closed`, close the incident issue
|
|||
within GitLab. Closing the issue triggers a background worker to update the
|
||||
GitLab Status Page website.
|
||||
|
||||
If you make a published issue confidential, GitLab unpublishes it from your
|
||||
GitLab Status Page website.
|
||||
If you
|
||||
[make a published issue confidential](../../user/project/issues/confidential_issues.md#making-an-issue-confidential),
|
||||
GitLab unpublishes it from your GitLab Status Page website.
|
||||
|
|
|
@ -28,7 +28,7 @@ If the following conditions are met, a **Request CVE ID** button appears in your
|
|||
- The project is hosted in GitLab.com.
|
||||
- The project is public.
|
||||
- You are a maintainer of the project.
|
||||
- The issue is confidential.
|
||||
- The issue is [confidential](../project/issues/confidential_issues.md).
|
||||
|
||||
## Submitting a CVE ID Request
|
||||
|
||||
|
@ -37,7 +37,7 @@ the [GitLab CVE project](https://gitlab.com/gitlab-org/cves).
|
|||
|
||||
![CVE ID request button](img/cve_id_request_button.png)
|
||||
|
||||
Creating the confidential issue starts the CVE request process.
|
||||
Creating the [confidential issue](../project/issues/confidential_issues.md) starts the CVE request process.
|
||||
|
||||
![New CVE ID request issue](img/new_cve_request_issue.png)
|
||||
|
||||
|
|
|
@ -138,6 +138,8 @@ who have at least the Reporter role.
|
|||
|
||||
![Confidential comments](img/confidential_comments_v13_9.png)
|
||||
|
||||
You can also make an [entire issue confidential](../project/issues/confidential_issues.md).
|
||||
|
||||
## Show only comments
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/26723) in GitLab 11.5.
|
||||
|
|
|
@ -193,7 +193,10 @@ or newest items to be shown first.
|
|||
If you're working on items that contain private information, you can make an epic confidential.
|
||||
|
||||
NOTE:
|
||||
A confidential epic can only contain confidential issues and confidential child epics.
|
||||
A confidential epic can only contain [confidential issues](../../project/issues/confidential_issues.md)
|
||||
and confidential child epics. However, merge requests are public, if created in a public project.
|
||||
Read [Merge requests for confidential issues](../../project/merge_requests/confidential.md)
|
||||
to learn how to create a confidential merge request.
|
||||
|
||||
To make an epic confidential:
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ You can also filter epics in the Roadmap view by the epics':
|
|||
- Author
|
||||
- Label
|
||||
- Milestone
|
||||
- Confidentiality
|
||||
- [Confidentiality](../epics/manage_epics.md#make-an-epic-confidential)
|
||||
- Epic
|
||||
- Your Reaction
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ The following table lists project permissions available for each role:
|
|||
| Action | Guest | Reporter | Developer |Maintainer| Owner |
|
||||
|---------------------------------------------------|---------|------------|-------------|----------|--------|
|
||||
| Assign issues | ✓ (*16*)| ✓ | ✓ | ✓ | ✓ |
|
||||
| Create confidential issue | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create [confidential issue](project/issues/confidential_issues.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download and browse job artifacts | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download project | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
@ -97,7 +97,7 @@ The following table lists project permissions available for each role:
|
|||
| [Set issue estimate and record time spent](project/time_tracking.md) | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View CI/CD analytics | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View Code Review analytics **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View confidential issues | (*2*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| View [confidential issues](project/issues/confidential_issues.md) | (*2*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View License list **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ |
|
||||
|
@ -198,7 +198,7 @@ The following table lists project permissions available for each role:
|
|||
| Remove protected branches (*4*) | | | | | |
|
||||
|
||||
1. Guest users are able to perform this action on public and internal projects, but not private projects. This doesn't apply to [external users](#external-users) where explicit access must be given even if the project is internal.
|
||||
1. Guest users can only view the confidential issues they created themselves.
|
||||
1. Guest users can only view the [confidential issues](project/issues/confidential_issues.md) they created themselves.
|
||||
1. If **Public pipelines** is enabled in **Project Settings > CI/CD**.
|
||||
1. Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [protected branches](project/protected_branches.md).
|
||||
1. If the [branch is protected](project/protected_branches.md), this depends on the access Developers and Maintainers are given.
|
||||
|
@ -256,7 +256,7 @@ Read through the documentation on [permissions for File Locking](project/file_lo
|
|||
|
||||
### Confidential Issues permissions
|
||||
|
||||
Confidential issues can be accessed by users with reporter and higher permission levels,
|
||||
[Confidential issues](project/issues/confidential_issues.md) can be accessed by users with reporter and higher permission levels,
|
||||
as well as by guest users that create a confidential issue. To learn more,
|
||||
read through the documentation on [permissions and access to confidential issues](project/issues/confidential_issues.md#permissions-and-access-to-confidential-issues).
|
||||
|
||||
|
|
|
@ -45,8 +45,8 @@ system note in the issue's comments.
|
|||
## Indications of a confidential issue
|
||||
|
||||
There are a few things that visually separate a confidential issue from a
|
||||
regular one. In the issues index page view, you can see the eye-slash icon
|
||||
next to the issues that are marked as confidential.
|
||||
regular one. In the issues index page view, you can see the eye-slash (**(eye-slash)**) icon
|
||||
next to the issues that are marked as confidential:
|
||||
|
||||
![Confidential issues index page](img/confidential_issues_index_page.png)
|
||||
|
||||
|
@ -91,3 +91,6 @@ sees in the project's search results respectively.
|
|||
## Related links
|
||||
|
||||
- [Merge requests for confidential issues](../merge_requests/confidential.md)
|
||||
- [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential)
|
||||
- [Mark a comment as confidential](../../discussions/index.md#mark-a-comment-as-confidential)
|
||||
- [Security practices for confidential merge requests](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer) at GitLab
|
||||
|
|
|
@ -59,7 +59,7 @@ When you're creating a new issue, these are the fields you can fill in:
|
|||
|
||||
- Title
|
||||
- Description
|
||||
- Checkbox to make the issue confidential
|
||||
- Checkbox to make the issue [confidential](confidential_issues.md)
|
||||
- Assignee
|
||||
- Weight
|
||||
- [Epic](../../group/epics/index.md)
|
||||
|
|
|
@ -70,4 +70,6 @@ to the public upstream project.
|
|||
## Related links
|
||||
|
||||
- [Confidential issues](../issues/confidential_issues.md)
|
||||
- [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential)
|
||||
- [Mark a comment as confidential](../../discussions/index.md#mark-a-comment-as-confidential)
|
||||
- [Security practices for confidential merge requests](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer) at GitLab
|
||||
|
|
|
@ -140,7 +140,7 @@ when merged.
|
|||
|
||||
If the issue is [confidential](../issues/confidential_issues.md),
|
||||
you may want to use a different workflow for
|
||||
[merge requests for confidential issues](../issues/confidential_issues.md#merge-requests-for-confidential-issues)
|
||||
[merge requests for confidential issues](confidential.md)
|
||||
to prevent confidential information from being exposed.
|
||||
|
||||
### Deleting the source branch
|
||||
|
|
|
@ -12470,9 +12470,6 @@ msgstr ""
|
|||
msgid "Environments Dashboard"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments allow you to track deployments of your application. %{linkStart}More information%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
@ -43,7 +44,9 @@ describe('~/environments/components/edit.vue', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const fillForm = async (expected, response) => {
|
||||
const showsLoading = () => wrapper.find(GlLoadingIcon).exists();
|
||||
|
||||
const submitForm = async (expected, response) => {
|
||||
mock
|
||||
.onPut(DEFAULT_OPTS.provide.updateEnvironmentPath, {
|
||||
name: expected.name,
|
||||
|
@ -72,10 +75,20 @@ describe('~/environments/components/edit.vue', () => {
|
|||
expect(input().element.value).toBe(value);
|
||||
});
|
||||
|
||||
it('shows loader after form is submitted', async () => {
|
||||
const expected = { name: 'test', url: 'https://google.ca' };
|
||||
|
||||
expect(showsLoading()).toBe(false);
|
||||
|
||||
await submitForm(expected, [200, { path: '/test' }]);
|
||||
|
||||
expect(showsLoading()).toBe(true);
|
||||
});
|
||||
|
||||
it('submits the updated environment on submit', async () => {
|
||||
const expected = { name: 'test', url: 'https://google.ca' };
|
||||
|
||||
await fillForm(expected, [200, { path: '/test' }]);
|
||||
await submitForm(expected, [200, { path: '/test' }]);
|
||||
|
||||
expect(visitUrl).toHaveBeenCalledWith('/test');
|
||||
});
|
||||
|
@ -83,8 +96,9 @@ describe('~/environments/components/edit.vue', () => {
|
|||
it('shows errors on error', async () => {
|
||||
const expected = { name: 'test', url: 'https://google.ca' };
|
||||
|
||||
await fillForm(expected, [400, { message: ['name taken'] }]);
|
||||
await submitForm(expected, [400, { message: ['name taken'] }]);
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({ message: 'name taken' });
|
||||
expect(showsLoading()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,97 +1,105 @@
|
|||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import EnvironmentForm from '~/environments/components/environment_form.vue';
|
||||
|
||||
jest.mock('~/lib/utils/csrf');
|
||||
|
||||
const DEFAULT_OPTS = {
|
||||
propsData: {
|
||||
environment: { name: '', externalUrl: '' },
|
||||
title: 'environment',
|
||||
cancelPath: '/cancel',
|
||||
},
|
||||
const DEFAULT_PROPS = {
|
||||
environment: { name: '', externalUrl: '' },
|
||||
title: 'environment',
|
||||
cancelPath: '/cancel',
|
||||
};
|
||||
|
||||
describe('~/environments/components/form.vue', () => {
|
||||
let wrapper;
|
||||
|
||||
const createWrapper = (opts = {}) =>
|
||||
const createWrapper = (propsData = {}) =>
|
||||
mountExtended(EnvironmentForm, {
|
||||
...DEFAULT_OPTS,
|
||||
...opts,
|
||||
propsData: {
|
||||
...DEFAULT_PROPS,
|
||||
...propsData,
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = createWrapper();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('links to documentation regarding environments', () => {
|
||||
const link = wrapper.findByRole('link', { name: 'More information' });
|
||||
expect(link.attributes('href')).toBe('/help/ci/environments/index.md');
|
||||
});
|
||||
|
||||
it('links the cancel button to the cancel path', () => {
|
||||
const cancel = wrapper.findByRole('link', { name: 'Cancel' });
|
||||
|
||||
expect(cancel.attributes('href')).toBe(DEFAULT_OPTS.propsData.cancelPath);
|
||||
});
|
||||
|
||||
describe('name input', () => {
|
||||
let name;
|
||||
|
||||
describe('default', () => {
|
||||
beforeEach(() => {
|
||||
name = wrapper.findByLabelText('Name');
|
||||
wrapper = createWrapper();
|
||||
});
|
||||
|
||||
it('should emit changes to the name', async () => {
|
||||
await name.setValue('test');
|
||||
await name.trigger('blur');
|
||||
|
||||
expect(wrapper.emitted('change')).toEqual([[{ name: 'test', externalUrl: '' }]]);
|
||||
it('links to documentation regarding environments', () => {
|
||||
const link = wrapper.findByRole('link', { name: 'More information' });
|
||||
expect(link.attributes('href')).toBe('/help/ci/environments/index.md');
|
||||
});
|
||||
|
||||
it('should validate that the name is required', async () => {
|
||||
await name.setValue('');
|
||||
await name.trigger('blur');
|
||||
it('links the cancel button to the cancel path', () => {
|
||||
const cancel = wrapper.findByRole('link', { name: 'Cancel' });
|
||||
|
||||
expect(wrapper.findByText('This field is required').exists()).toBe(true);
|
||||
expect(name.attributes('aria-invalid')).toBe('true');
|
||||
expect(cancel.attributes('href')).toBe(DEFAULT_PROPS.cancelPath);
|
||||
});
|
||||
|
||||
describe('name input', () => {
|
||||
let name;
|
||||
|
||||
beforeEach(() => {
|
||||
name = wrapper.findByLabelText('Name');
|
||||
});
|
||||
|
||||
it('should emit changes to the name', async () => {
|
||||
await name.setValue('test');
|
||||
await name.trigger('blur');
|
||||
|
||||
expect(wrapper.emitted('change')).toEqual([[{ name: 'test', externalUrl: '' }]]);
|
||||
});
|
||||
|
||||
it('should validate that the name is required', async () => {
|
||||
await name.setValue('');
|
||||
await name.trigger('blur');
|
||||
|
||||
expect(wrapper.findByText('This field is required').exists()).toBe(true);
|
||||
expect(name.attributes('aria-invalid')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('url input', () => {
|
||||
let url;
|
||||
|
||||
beforeEach(() => {
|
||||
url = wrapper.findByLabelText('External URL');
|
||||
});
|
||||
|
||||
it('should emit changes to the url', async () => {
|
||||
await url.setValue('https://example.com');
|
||||
await url.trigger('blur');
|
||||
|
||||
expect(wrapper.emitted('change')).toEqual([
|
||||
[{ name: '', externalUrl: 'https://example.com' }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should validate that the url is required', async () => {
|
||||
await url.setValue('example.com');
|
||||
await url.trigger('blur');
|
||||
|
||||
expect(wrapper.findByText('The URL should start with http:// or https://').exists()).toBe(
|
||||
true,
|
||||
);
|
||||
expect(url.attributes('aria-invalid')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
it('submits when the form does', async () => {
|
||||
await wrapper.findByRole('form', { title: 'environment' }).trigger('submit');
|
||||
|
||||
expect(wrapper.emitted('submit')).toEqual([[]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('url input', () => {
|
||||
let url;
|
||||
|
||||
beforeEach(() => {
|
||||
url = wrapper.findByLabelText('External URL');
|
||||
});
|
||||
|
||||
it('should emit changes to the url', async () => {
|
||||
await url.setValue('https://example.com');
|
||||
await url.trigger('blur');
|
||||
|
||||
expect(wrapper.emitted('change')).toEqual([
|
||||
[{ name: '', externalUrl: 'https://example.com' }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should validate that the url is required', async () => {
|
||||
await url.setValue('example.com');
|
||||
await url.trigger('blur');
|
||||
|
||||
expect(wrapper.findByText('The URL should start with http:// or https://').exists()).toBe(
|
||||
true,
|
||||
);
|
||||
expect(url.attributes('aria-invalid')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
it('submits when the form does', async () => {
|
||||
await wrapper.findByRole('form', { title: 'environment' }).trigger('submit');
|
||||
|
||||
expect(wrapper.emitted('submit')).toEqual([[]]);
|
||||
it('shows a loading icon while loading', () => {
|
||||
wrapper = createWrapper({ loading: true });
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
@ -39,7 +40,9 @@ describe('~/environments/components/new.vue', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const fillForm = async (expected, response) => {
|
||||
const showsLoading = () => wrapper.find(GlLoadingIcon).exists();
|
||||
|
||||
const submitForm = async (expected, response) => {
|
||||
mock
|
||||
.onPost(DEFAULT_OPTS.provide.projectEnvironmentsPath, {
|
||||
name: expected.name,
|
||||
|
@ -68,10 +71,20 @@ describe('~/environments/components/new.vue', () => {
|
|||
expect(input().element.value).toBe(value);
|
||||
});
|
||||
|
||||
it('shows loader after form is submitted', async () => {
|
||||
const expected = { name: 'test', url: 'https://google.ca' };
|
||||
|
||||
expect(showsLoading()).toBe(false);
|
||||
|
||||
await submitForm(expected, [200, { path: '/test' }]);
|
||||
|
||||
expect(showsLoading()).toBe(true);
|
||||
});
|
||||
|
||||
it('submits the new environment on submit', async () => {
|
||||
const expected = { name: 'test', url: 'https://google.ca' };
|
||||
|
||||
await fillForm(expected, [200, { path: '/test' }]);
|
||||
await submitForm(expected, [200, { path: '/test' }]);
|
||||
|
||||
expect(visitUrl).toHaveBeenCalledWith('/test');
|
||||
});
|
||||
|
@ -79,8 +92,9 @@ describe('~/environments/components/new.vue', () => {
|
|||
it('shows errors on error', async () => {
|
||||
const expected = { name: 'test', url: 'https://google.ca' };
|
||||
|
||||
await fillForm(expected, [400, { message: ['name taken'] }]);
|
||||
await submitForm(expected, [400, { message: ['name taken'] }]);
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({ message: 'name taken' });
|
||||
expect(showsLoading()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue