diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue index 44d681b71bd8beabcad71dad5611b960fdf02d08..1532a7428f675a27ad128cbd0f9da4410474666b 100644 --- a/app/assets/javascripts/environments/components/edit_environment.vue +++ b/app/assets/javascripts/environments/components/edit_environment.vue @@ -2,7 +2,6 @@ import { GlLoadingIcon } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { visitUrl } from '~/lib/utils/url_utility'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import getEnvironment from '../graphql/queries/environment.query.graphql'; import updateEnvironment from '../graphql/mutations/update_environment.mutation.graphql'; import EnvironmentForm from './environment_form.vue'; @@ -12,11 +11,9 @@ export default { GlLoadingIcon, EnvironmentForm, }, - mixins: [glFeatureFlagsMixin()], inject: ['projectEnvironmentsPath', 'projectPath', 'environmentName'], apollo: { - // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties - environment: { + formEnvironment: { query: getEnvironment, variables() { return { @@ -26,7 +23,7 @@ export default { }, update(data) { const result = data?.project?.environment || {}; - this.formEnvironment = { ...result, clusterAgentId: result?.clusterAgent?.id }; + return { ...result, clusterAgentId: result?.clusterAgent?.id }; }, }, }, @@ -38,7 +35,7 @@ export default { }, computed: { isQueryLoading() { - return this.$apollo.queries.environment.loading; + return this.$apollo.queries.formEnvironment.loading; }, }, methods: { @@ -53,6 +50,7 @@ export default { variables: { input: { id: this.formEnvironment.id, + description: this.formEnvironment.description, externalUrl: this.formEnvironment.externalUrl, clusterAgentId: this.formEnvironment.clusterAgentId, kubernetesNamespace: this.formEnvironment.kubernetesNamespace, diff --git a/app/assets/javascripts/environments/components/environment_form.vue b/app/assets/javascripts/environments/components/environment_form.vue index f0ea119a986ff4fb0aeeba90beb9adb8661efa70..b3c9fecff72fad5a9f44bda954466a89d395265d 100644 --- a/app/assets/javascripts/environments/components/environment_form.vue +++ b/app/assets/javascripts/environments/components/environment_form.vue @@ -16,7 +16,7 @@ import { ENVIRONMENT_EDIT_HELP_TEXT, } from 'ee_else_ce/environments/constants'; import csrf from '~/lib/utils/csrf'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import getUserAuthorizedAgents from '../graphql/queries/user_authorized_agents.query.graphql'; import EnvironmentFluxResourceSelector from './environment_flux_resource_selector.vue'; @@ -33,12 +33,13 @@ export default { GlSprintf, EnvironmentFluxResourceSelector, EnvironmentNamespaceSelector, + MarkdownEditor, }, - mixins: [glFeatureFlagsMixin()], inject: { protectedEnvironmentSettingsPath: { default: '' }, projectPath: { default: '' }, kasTunnelUrl: { default: '' }, + markdownPreviewPath: { default: '' }, }, props: { environment: { @@ -71,6 +72,11 @@ export default { nameFeedback: __('This field is required'), nameDisabledHelp: __("You cannot rename an environment after it's created."), nameDisabledLinkText: __('How do I rename an environment?'), + descriptionLabel: __('Description'), + descriptionPlaceholder: s__('Environments|Write a description or drag your files here…'), + descriptionHelpText: s__( + 'Environments|The description is displayed to anyone who can see this environment.', + ), urlLabel: __('External URL'), urlFeedback: __('The URL should start with http:// or https://'), agentLabel: s__('Environments|GitLab agent'), @@ -84,6 +90,8 @@ export default { renamingDisabledHelpPagePath: helpPagePath('ci/environments/index.md', { anchor: 'rename-an-environment', }), + markdownDocsPath: helpPagePath('user/markdown'), + restrictedToolbarItems: ['full-screen'], data() { return { visited: { @@ -158,6 +166,13 @@ export default { credentials: 'include', }; }, + descriptionFieldProps() { + return { + 'aria-label': this.$options.i18n.descriptionLabel, + placeholder: this.$options.i18n.descriptionPlaceholder, + id: 'environment_description', + }; + }, }, watch: { environment(change) { @@ -172,6 +187,11 @@ export default { visit(field) { this.visited[field] = true; }, + updateDescription($event) { + if (this.environment.description !== $event) { + this.onChange({ ...this.environment, description: $event }); + } + }, getAgentsList() { this.$apollo.addSmartQuery('userAccessAuthorizedAgents', { variables() { @@ -253,6 +273,24 @@ export default { @blur="visit('name')" /> </gl-form-group> + <gl-form-group + :label="$options.i18n.descriptionLabel" + :description="$options.i18n.descriptionHelpText" + label-for="environment_description" + :state="valid.description" + > + <div class="common-note-form gfm-form"> + <markdown-editor + :value="environment.description" + :render-markdown-path="markdownPreviewPath" + :form-field-props="descriptionFieldProps" + :restricted-tool-bar-items="$options.restrictedToolbarItems" + :markdown-docs-path="$options.markdownDocsPath" + :disabled="loading" + @input="updateDescription" + /> + </div> + </gl-form-group> <gl-form-group :label="$options.i18n.urlLabel" :state="valid.url" @@ -320,6 +358,7 @@ export default { variant="confirm" name="commit" class="js-no-auto-disable" + data-testid="save-environment" >{{ $options.i18n.save }}</gl-button > <gl-button :href="cancelPath">{{ $options.i18n.cancel }}</gl-button> diff --git a/app/assets/javascripts/environments/components/environments_detail_header.vue b/app/assets/javascripts/environments/components/environments_detail_header.vue index 19e2d42bf4f76cef5ad30db0ad355c683e96c4e2..d8b33d73b0620d18594103753b9ef938434750e9 100644 --- a/app/assets/javascripts/environments/components/environments_detail_header.vue +++ b/app/assets/javascripts/environments/components/environments_detail_header.vue @@ -1,9 +1,17 @@ <script> -import { GlButton, GlModalDirective, GlTooltipDirective as GlTooltip, GlSprintf } from '@gitlab/ui'; +import { + GlButton, + GlTruncateText, + GlModalDirective, + GlTooltipDirective as GlTooltip, + GlSprintf, +} from '@gitlab/ui'; import csrf from '~/lib/utils/csrf'; import { __, s__ } from '~/locale'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; +import { renderGFM } from '~/behaviors/markdown/render_gfm'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import DeleteEnvironmentModal from './delete_environment_modal.vue'; import StopEnvironmentModal from './stop_environment_modal.vue'; import DeployFreezeAlert from './deploy_freeze_alert.vue'; @@ -14,6 +22,7 @@ export default { components: { GlButton, GlSprintf, + GlTruncateText, TimeAgo, DeployFreezeAlert, DeleteEnvironmentModal, @@ -22,6 +31,7 @@ export default { directives: { GlModalDirective, GlTooltip, + SafeHtml, }, mixins: [timeagoMixin], props: { @@ -69,6 +79,7 @@ export default { externalButtonTitle: s__('Environments|Open live environment'), externalButtonText: __('View deployment'), cancelAutoStopButtonTitle: __('Prevent environment from auto-stopping'), + showMoreText: __('Read more'), }, computed: { shouldShowCancelAutoStopButton() { @@ -84,6 +95,10 @@ export default { return this.canAdminEnvironment && this.environment.hasTerminals; }, }, + mounted() { + renderGFM(this.$refs['gfm-content']); + }, + safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] }, }; </script> <template> @@ -159,5 +174,18 @@ export default { <delete-environment-modal v-if="canDestroyEnvironment" :environment="environment" /> <stop-environment-modal v-if="shouldShowStopButton" :environment="environment" /> </header> + + <gl-truncate-text + v-if="environment.descriptionHtml" + :show-more-text="$options.i18n.showMoreText" + class="gl-relative gl-mb-4" + > + <div + ref="gfm-content" + v-safe-html:[$options.safeHtmlConfig]="environment.descriptionHtml" + class="md" + data-testid="environment-description-content" + ></div> + </gl-truncate-text> </div> </template> diff --git a/app/assets/javascripts/environments/components/new_environment.vue b/app/assets/javascripts/environments/components/new_environment.vue index 6a4ed34989f60844e429bc8655b291c9c4f356d0..11b63d1e0993153d607e9f61efce249b7ce10248 100644 --- a/app/assets/javascripts/environments/components/new_environment.vue +++ b/app/assets/javascripts/environments/components/new_environment.vue @@ -13,6 +13,7 @@ export default { return { environment: { name: '', + description: '', externalUrl: '', clusterAgentId: null, }, @@ -31,6 +32,7 @@ export default { variables: { input: { name: this.environment.name, + description: this.environment.description, externalUrl: this.environment.externalUrl, projectPath: this.projectPath, clusterAgentId: this.environment.clusterAgentId, diff --git a/app/assets/javascripts/environments/edit.js b/app/assets/javascripts/environments/edit.js index 3f22b83e618e4fa29d12748efe8132511b2040c5..c58a0152d62ea8469294071d965c2295c894018f 100644 --- a/app/assets/javascripts/environments/edit.js +++ b/app/assets/javascripts/environments/edit.js @@ -15,6 +15,7 @@ export default (el) => { projectEnvironmentsPath, protectedEnvironmentSettingsPath, projectPath, + markdownPreviewPath, environmentName, kasTunnelUrl, } = el.dataset; @@ -26,6 +27,7 @@ export default (el) => { projectEnvironmentsPath, protectedEnvironmentSettingsPath, projectPath, + markdownPreviewPath, environmentName, kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl), }, diff --git a/app/assets/javascripts/environments/graphql/queries/environment.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment.query.graphql index 2d6faed5c88a73dc02e6c4a631633d5ad3403b00..0df0c5aa16062f7cd0f48efeb51bd877ad9cab0c 100644 --- a/app/assets/javascripts/environments/graphql/queries/environment.query.graphql +++ b/app/assets/javascripts/environments/graphql/queries/environment.query.graphql @@ -4,6 +4,7 @@ query getEnvironment($projectFullPath: ID!, $environmentName: String) { environment(name: $environmentName) { id name + description externalUrl kubernetesNamespace fluxResourcePath diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js index 523c27022bb1bb96941131cfc8e36ef8cf1774bf..38ef597cd3dcc567c986ce7071f2e84032b3c930 100644 --- a/app/assets/javascripts/environments/mount_show.js +++ b/app/assets/javascripts/environments/mount_show.js @@ -37,9 +37,10 @@ export const initHeader = () => { autoStopAt: dataset.autoStopAt, onSingleEnvironmentPage: true, // TODO: These two props are snake_case because the environments_mixin file uses - // them and the mixin is imported in several files. It would be nice to conver them to camelCase. + // them and the mixin is imported in several files. It would be nice to convert them to camelCase. stop_path: dataset.environmentStopPath, delete_path: dataset.environmentDeletePath, + descriptionHtml: dataset.descriptionHtml, }; return { diff --git a/app/assets/javascripts/environments/new.js b/app/assets/javascripts/environments/new.js index 652085b1f28905b769b8f2c3441707f1b564b591..92de3ae5b2ba0a86a4c791831a6cfbace4512d23 100644 --- a/app/assets/javascripts/environments/new.js +++ b/app/assets/javascripts/environments/new.js @@ -11,7 +11,7 @@ export default (el) => { return null; } - const { projectEnvironmentsPath, projectPath, kasTunnelUrl } = el.dataset; + const { projectEnvironmentsPath, projectPath, markdownPreviewPath, kasTunnelUrl } = el.dataset; return new Vue({ el, @@ -19,6 +19,7 @@ export default (el) => { provide: { projectEnvironmentsPath, projectPath, + markdownPreviewPath, kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl), }, render(h) { diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 88007841df1f1f44c4c99740fc7c97df0ebe8f14..e8ab2f72c93ecab60706c82487be4e5b036f6450 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -32,6 +32,7 @@ def environments_detail_data(user, project, environment) environment_terminal_path: terminal_project_environment_path(project, environment), has_terminals: environment.has_terminals?, is_environment_available: environment.available?, + description_html: markdown_field(environment, :description), auto_stop_at: environment.auto_stop_at, graphql_etag_key: environment.etag_cache_key } diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml index 4f9b093a91f0eb66a4ee4610e4cfb5eb8b0ec59c..4664fed282f6006fdb31bc323976b75d5e75b189 100644 --- a/app/views/projects/environments/edit.html.haml +++ b/app/views/projects/environments/edit.html.haml @@ -6,6 +6,7 @@ #js-edit-environment{ data: { project_environments_path: project_environments_path(@project), protected_environment_settings_path: (project_settings_ci_cd_path(@project, anchor: 'js-protected-environments-settings') if @project.licensed_feature_available?(:protected_environments)), + markdown_preview_path: preview_markdown_path(@project), project_path: @project.full_path, environment_name: @environment.name, kas_tunnel_url: ::Gitlab::Kas.tunnel_url } } diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index a25dd5fdec6a752a86c3140615e6c264bcad2599..769d21fb86b617d59214dba6ba10693510750653 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -3,4 +3,4 @@ - page_title s_("Environments|New environment") - add_page_specific_style 'page_bundles/environments' -#js-new-environment{ data: { project_environments_path: project_environments_path(@project), project_path: @project.full_path, kas_tunnel_url: ::Gitlab::Kas.tunnel_url } } +#js-new-environment{ data: { project_environments_path: project_environments_path(@project), project_path: @project.full_path, kas_tunnel_url: ::Gitlab::Kas.tunnel_url, markdown_preview_path: preview_markdown_path(@project) } } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 438809ccddc10eb832a45ab89cb5606566845b8e..122acaec222f5782d88278e2aa6dbc7b0ef896aa 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -21490,6 +21490,9 @@ msgstr "" msgid "Environments|Synced" msgstr "" +msgid "Environments|The description is displayed to anyone who can see this environment." +msgstr "" + msgid "Environments|There are no deployments for this environment yet. %{linkStart}Learn more about setting up deployments.%{linkEnd}" msgstr "" @@ -21517,6 +21520,9 @@ msgstr "" msgid "Environments|View logs" msgstr "" +msgid "Environments|Write a description or drag your files here…" +msgstr "" + msgid "Environments|by %{avatar}" msgstr "" diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js index 62b62bbc8265c76458a1034c7bf434ffd80457a3..364d821994c849426f912a730f248ddfd32c61e8 100644 --- a/spec/frontend/environments/edit_environment_spec.js +++ b/spec/frontend/environments/edit_environment_spec.js @@ -17,6 +17,7 @@ const environment = { id: '1', name: 'foo', externalUrl: 'https://foo.example.com', + description: 'this is description', clusterAgent: null, kubernetesNamespace: null, fluxResourcePath: null, @@ -78,6 +79,7 @@ describe('~/environments/components/edit.vue', () => { const findForm = () => wrapper.findByRole('form', { name: 'Edit environment' }); const showsLoading = () => wrapper.findComponent(GlLoadingIcon).exists(); + const showsFormLoading = () => wrapper.findByTestId('save-environment').props('loading'); describe('default', () => { it('performs the environment apollo query', () => { @@ -127,11 +129,11 @@ describe('~/environments/components/edit.vue', () => { }); it('shows loader after form is submitted', async () => { - expect(showsLoading()).toBe(false); + expect(showsFormLoading()).toBe(false); await findForm().trigger('submit'); - expect(showsLoading()).toBe(true); + expect(showsFormLoading()).toBe(true); }); it('submits the updated environment on submit', async () => { @@ -154,7 +156,7 @@ describe('~/environments/components/edit.vue', () => { await waitForPromises(); expect(createAlert).toHaveBeenCalledWith({ message: 'uh oh!' }); - expect(showsLoading()).toBe(false); + expect(showsFormLoading()).toBe(false); }); }); }); diff --git a/spec/frontend/environments/environment_form_spec.js b/spec/frontend/environments/environment_form_spec.js index ad00617ce906d44e10f9964355e2ad99d694d050..cb7bbfb3874865fc277b3c6d1468cdd90e709400 100644 --- a/spec/frontend/environments/environment_form_spec.js +++ b/spec/frontend/environments/environment_form_spec.js @@ -3,6 +3,7 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import waitForPromises from 'helpers/wait_for_promises'; import { mountExtended } from 'helpers/vue_test_utils_helper'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import EnvironmentForm from '~/environments/components/environment_form.vue'; import getUserAuthorizedAgents from '~/environments/graphql/queries/user_authorized_agents.query.graphql'; import EnvironmentFluxResourceSelector from '~/environments/components/environment_flux_resource_selector.vue'; @@ -13,7 +14,7 @@ import { mockKasTunnelUrl } from './mock_data'; jest.mock('~/lib/utils/csrf'); const DEFAULT_PROPS = { - environment: { name: '', externalUrl: '' }, + environment: { name: '', externalUrl: '', description: '' }, title: 'environment', cancelPath: '/cancel', }; @@ -21,6 +22,7 @@ const DEFAULT_PROPS = { const PROVIDE = { protectedEnvironmentSettingsPath: '/projects/not_real/settings/ci_cd', kasTunnelUrl: mockKasTunnelUrl, + markdownPreviewPath: '/path/to/markdown/preview', }; const userAccessAuthorizedAgents = [ { agent: { id: '1', name: 'agent-1' } }, @@ -89,6 +91,7 @@ describe('~/environments/components/form.vue', () => { const findAgentSelector = () => wrapper.findByTestId('agent-selector'); const findNamespaceSelector = () => wrapper.findComponent(EnvironmentNamespaceSelector); const findFluxResourceSelector = () => wrapper.findComponent(EnvironmentFluxResourceSelector); + const findMarkdownField = () => wrapper.findComponent(MarkdownEditor); const selectAgent = async () => { findAgentSelector().vm.$emit('shown'); @@ -128,7 +131,9 @@ describe('~/environments/components/form.vue', () => { await name.setValue('test'); await name.trigger('blur'); - expect(wrapper.emitted('change')).toEqual([[{ name: 'test', externalUrl: '' }]]); + expect(wrapper.emitted('change')).toEqual([ + [{ name: 'test', externalUrl: '', description: '' }], + ]); }); it('should validate that the name is required', async () => { @@ -152,7 +157,7 @@ describe('~/environments/components/form.vue', () => { await url.trigger('blur'); expect(wrapper.emitted('change')).toEqual([ - [{ name: '', externalUrl: 'https://example.com' }], + [{ name: '', externalUrl: 'https://example.com', description: '' }], ]); }); @@ -185,6 +190,7 @@ describe('~/environments/components/form.vue', () => { environment: { name: '', externalUrl: '', + description: '', }, }); }); @@ -226,6 +232,7 @@ describe('~/environments/components/form.vue', () => { id: 1, name: 'test', externalUrl: 'https://example.com', + description: '', }, }); }); @@ -255,6 +262,42 @@ describe('~/environments/components/form.vue', () => { }); }); + describe('description', () => { + it('renders markdown field', () => { + wrapper = createWrapper(); + + expect(findMarkdownField().props()).toMatchObject({ + value: '', + renderMarkdownPath: PROVIDE.markdownPreviewPath, + markdownDocsPath: '/help/user/markdown', + disabled: false, + }); + }); + + it('sets the markdown value when provided', () => { + wrapper = createWrapper({ + environment: { name: 'production', externalUrl: '', description: 'some-description' }, + }); + + expect(findMarkdownField().props('value')).toBe('some-description'); + }); + + it('emits changes on user input', async () => { + wrapper = createWrapper(); + await findMarkdownField().vm.$emit('input', 'my new description'); + + expect(wrapper.emitted('change').at(-1)).toEqual([ + { name: '', externalUrl: '', description: 'my new description' }, + ]); + }); + + it('disables field on loading', () => { + wrapper = createWrapper({ loading: true }); + + expect(findMarkdownField().props('disabled')).toBe(true); + }); + }); + describe('agent selector', () => { beforeEach(() => { wrapper = createWrapperWithApollo(); @@ -301,6 +344,7 @@ describe('~/environments/components/form.vue', () => { { name: '', externalUrl: '', + description: '', clusterAgentId: '2', kubernetesNamespace: null, fluxResourcePath: null, @@ -334,7 +378,13 @@ describe('~/environments/components/form.vue', () => { await nextTick(); expect(wrapper.emitted('change')[1]).toEqual([ - { name: '', externalUrl: '', kubernetesNamespace: 'agent', fluxResourcePath: null }, + { + name: '', + externalUrl: '', + description: '', + kubernetesNamespace: 'agent', + fluxResourcePath: null, + }, ]); }); }); diff --git a/spec/frontend/environments/environments_detail_header_spec.js b/spec/frontend/environments/environments_detail_header_spec.js index b59d9f457d67069c47c026013f3fdf915c28cb65..958cbd4bd6f52788f161a68fc3fbcb712d89ca18 100644 --- a/spec/frontend/environments/environments_detail_header_spec.js +++ b/spec/frontend/environments/environments_detail_header_spec.js @@ -27,6 +27,7 @@ describe('Environments detail header component', () => { const findStopEnvironmentModal = () => wrapper.findComponent(StopEnvironmentModal); const findDeleteEnvironmentModal = () => wrapper.findComponent(DeleteEnvironmentModal); const findDeployFreezeAlert = () => wrapper.findComponent(DeployFreezeAlert); + const findDescription = () => wrapper.findByTestId('environment-description-content'); const buttons = [ ['Cancel Auto Stop At', findCancelAutoStopAtButton], @@ -229,4 +230,22 @@ describe('Environments detail header component', () => { expect(findDeployFreezeAlert().props('name')).toBe(environment.name); }); }); + + describe('environment description', () => { + it.each` + condition | descriptionHtml | renderDescription + ${"doesn't render"} | ${''} | ${false} + ${'renders'} | ${'this is description'} | ${true} + `( + '$condition when `descriptionHtml` is "$descriptionHtml"', + ({ descriptionHtml, renderDescription }) => { + const environment = createEnvironment({ descriptionHtml }); + createWrapper({ + props: { environment }, + }); + + expect(findDescription().exists()).toBe(renderDescription); + }, + ); + }); }); diff --git a/spec/frontend/environments/new_environment_spec.js b/spec/frontend/environments/new_environment_spec.js index e95e5d4285220535775266a01d3716eb9937b37d..78cd4282af60691b040d901783838b1d0f5e9a8f 100644 --- a/spec/frontend/environments/new_environment_spec.js +++ b/spec/frontend/environments/new_environment_spec.js @@ -1,4 +1,3 @@ -import { GlLoadingIcon } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { mountExtended } from 'helpers/vue_test_utils_helper'; @@ -49,7 +48,7 @@ describe('~/environments/components/new.vue', () => { const findNameInput = () => wrapper.findByLabelText('Name'); const findExternalUrlInput = () => wrapper.findByLabelText('External URL'); const findForm = () => wrapper.findByRole('form', { name: 'New environment' }); - const showsLoading = () => wrapper.findComponent(GlLoadingIcon).exists(); + const showsLoading = () => wrapper.findByTestId('save-environment').props('loading'); const submitForm = async () => { await findNameInput().setValue('test'); diff --git a/spec/helpers/environment_helper_spec.rb b/spec/helpers/environment_helper_spec.rb index 4e842cfbcdc9c56139652914d31bcc62dbd744db..6c32355b616bd22df1de8613953874330a5accda 100644 --- a/spec/helpers/environment_helper_spec.rb +++ b/spec/helpers/environment_helper_spec.rb @@ -9,7 +9,9 @@ let_it_be(:auto_stop_at) { Time.now.utc } let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, :repository) } - let_it_be(:environment) { create(:environment, project: project, auto_stop_at: auto_stop_at) } + let_it_be(:environment) do + create(:environment, project: project, auto_stop_at: auto_stop_at, description: '_description_') + end before do allow(helper).to receive(:current_user).and_return(user) @@ -35,6 +37,7 @@ environment_terminal_path: terminal_project_environment_path(project, environment), has_terminals: false, is_environment_available: true, + description_html: '<p data-sourcepos="1:1-1:13" dir="auto"><em data-sourcepos="1:1-1:13">description</em></p>', auto_stop_at: auto_stop_at, graphql_etag_key: environment.etag_cache_key }.to_json)