diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-30 00:10:03 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-30 00:10:03 +0000 |
commit | 420215876fadcc44935e7937cb56f7ffe5212ecb (patch) | |
tree | a24028043ba417bbdde620521e1940eb4ce727e1 | |
parent | e4df6a7c53ab6e9dd6d63516ca9e0ad143cfa17a (diff) | |
download | gitlab-ce-420215876fadcc44935e7937cb56f7ffe5212ecb.tar.gz |
Add latest changes from gitlab-org/gitlab@master
22 files changed, 177 insertions, 124 deletions
diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue index 80355708311..1cd960d7cd6 100644 --- a/app/assets/javascripts/environments/components/edit_environment.vue +++ b/app/assets/javascripts/environments/components/edit_environment.vue @@ -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" /> diff --git a/app/assets/javascripts/environments/components/environment_form.vue b/app/assets/javascripts/environments/components/environment_form.vue index 636e263b990..6db8fe24e72 100644 --- a/app/assets/javascripts/environments/components/environment_form.vue +++ b/app/assets/javascripts/environments/components/environment_form.vue @@ -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> diff --git a/app/assets/javascripts/environments/components/new_environment.vue b/app/assets/javascripts/environments/components/new_environment.vue index 6899ad63921..14da2668417 100644 --- a/app/assets/javascripts/environments/components/new_environment.vue +++ b/app/assets/javascripts/environments/components/new_environment.vue @@ -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" /> diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue index 6ca96504982..0ecfdf420db 100644 --- a/app/assets/javascripts/security_configuration/components/feature_card.vue +++ b/app/assets/javascripts/security_configuration/components/feature_card.vue @@ -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 }} diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml deleted file mode 100644 index a295c8f6fb0..00000000000 --- a/app/views/projects/environments/_form.html.haml +++ /dev/null @@ -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' diff --git a/doc/development/iterating_tables_in_batches.md b/doc/development/iterating_tables_in_batches.md index 3f2b467fec9..fba6f2f616d 100644 --- a/doc/development/iterating_tables_in_batches.md +++ b/doc/development/iterating_tables_in_batches.md @@ -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. diff --git a/doc/development/permissions.md b/doc/development/permissions.md index 177fedcf454..b7079e9fb8e 100644 --- a/doc/development/permissions.md +++ b/doc/development/permissions.md @@ -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. diff --git a/doc/development/reference_processing.md b/doc/development/reference_processing.md index 2fd0ce51b39..ad6552e88fe 100644 --- a/doc/development/reference_processing.md +++ b/doc/development/reference_processing.md @@ -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. diff --git a/doc/operations/incident_management/status_page.md b/doc/operations/incident_management/status_page.md index d63d42e07c1..d14e4120511 100644 --- a/doc/operations/incident_management/status_page.md +++ b/doc/operations/incident_management/status_page.md @@ -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. diff --git a/doc/user/application_security/cve_id_request.md b/doc/user/application_security/cve_id_request.md index aaf701c91dc..009f8d828f7 100644 --- a/doc/user/application_security/cve_id_request.md +++ b/doc/user/application_security/cve_id_request.md @@ -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) diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index 825f9be6ba6..a1d8863710c 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -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. diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md index b063ba9a75c..1b36d6f03df 100644 --- a/doc/user/group/epics/manage_epics.md +++ b/doc/user/group/epics/manage_epics.md @@ -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: diff --git a/doc/user/group/roadmap/index.md b/doc/user/group/roadmap/index.md index 88d43715c58..811297c6eda 100644 --- a/doc/user/group/roadmap/index.md +++ b/doc/user/group/roadmap/index.md @@ -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 diff --git a/doc/user/permissions.md b/doc/user/permissions.md index d3b33f79f27..16509565525 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -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). diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md index e6705933ae9..136e8ee2ebb 100644 --- a/doc/user/project/issues/confidential_issues.md +++ b/doc/user/project/issues/confidential_issues.md @@ -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 diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md index c570bc9612a..a2185c83f4d 100644 --- a/doc/user/project/issues/managing_issues.md +++ b/doc/user/project/issues/managing_issues.md @@ -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) diff --git a/doc/user/project/merge_requests/confidential.md b/doc/user/project/merge_requests/confidential.md index b50c5ee0ea9..6df84dd1dd1 100644 --- a/doc/user/project/merge_requests/confidential.md +++ b/doc/user/project/merge_requests/confidential.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 diff --git a/doc/user/project/merge_requests/getting_started.md b/doc/user/project/merge_requests/getting_started.md index ce39f39f0a1..1c8ee587838 100644 --- a/doc/user/project/merge_requests/getting_started.md +++ b/doc/user/project/merge_requests/getting_started.md @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2a4f61d307f..6a0796bb5f0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js index de497b18dd6..3e7f5dd5ff4 100644 --- a/spec/frontend/environments/edit_environment_spec.js +++ b/spec/frontend/environments/edit_environment_spec.js @@ -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); }); }); diff --git a/spec/frontend/environments/environment_form_spec.js b/spec/frontend/environments/environment_form_spec.js index 2d7cdc8a0bd..ed8fda71dab 100644 --- a/spec/frontend/environments/environment_form_spec.js +++ b/spec/frontend/environments/environment_form_spec.js @@ -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' }); + describe('default', () => { + beforeEach(() => { + wrapper = createWrapper(); + }); - expect(cancel.attributes('href')).toBe(DEFAULT_OPTS.propsData.cancelPath); - }); + it('links to documentation regarding environments', () => { + const link = wrapper.findByRole('link', { name: 'More information' }); + expect(link.attributes('href')).toBe('/help/ci/environments/index.md'); + }); - describe('name input', () => { - let name; + it('links the cancel button to the cancel path', () => { + const cancel = wrapper.findByRole('link', { name: 'Cancel' }); - beforeEach(() => { - name = wrapper.findByLabelText('Name'); + expect(cancel.attributes('href')).toBe(DEFAULT_PROPS.cancelPath); }); - it('should emit changes to the name', async () => { - await name.setValue('test'); - await name.trigger('blur'); + describe('name input', () => { + let name; - expect(wrapper.emitted('change')).toEqual([[{ name: 'test', externalUrl: '' }]]); - }); + beforeEach(() => { + name = wrapper.findByLabelText('Name'); + }); - it('should validate that the name is required', async () => { - await name.setValue(''); - await name.trigger('blur'); + it('should emit changes to the name', async () => { + await name.setValue('test'); + await name.trigger('blur'); - expect(wrapper.findByText('This field is required').exists()).toBe(true); - expect(name.attributes('aria-invalid')).toBe('true'); - }); - }); + expect(wrapper.emitted('change')).toEqual([[{ name: 'test', externalUrl: '' }]]); + }); - describe('url input', () => { - let url; + it('should validate that the name is required', async () => { + await name.setValue(''); + await name.trigger('blur'); - beforeEach(() => { - url = wrapper.findByLabelText('External URL'); + expect(wrapper.findByText('This field is required').exists()).toBe(true); + expect(name.attributes('aria-invalid')).toBe('true'); + }); }); - it('should emit changes to the url', async () => { - await url.setValue('https://example.com'); - await url.trigger('blur'); + describe('url input', () => { + let url; + + beforeEach(() => { + url = wrapper.findByLabelText('External URL'); + }); - expect(wrapper.emitted('change')).toEqual([ - [{ name: '', externalUrl: 'https://example.com' }], - ]); + 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('should validate that the url is required', async () => { - await url.setValue('example.com'); - await url.trigger('blur'); + it('submits when the form does', async () => { + await wrapper.findByRole('form', { title: 'environment' }).trigger('submit'); - expect(wrapper.findByText('The URL should start with http:// or https://').exists()).toBe( - true, - ); - expect(url.attributes('aria-invalid')).toBe('true'); + expect(wrapper.emitted('submit')).toEqual([[]]); }); }); - 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); }); }); diff --git a/spec/frontend/environments/new_environment_spec.js b/spec/frontend/environments/new_environment_spec.js index b92c4a688b7..f6d970e02d8 100644 --- a/spec/frontend/environments/new_environment_spec.js +++ b/spec/frontend/environments/new_environment_spec.js @@ -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); }); }); |