Skip to content

Add form content to Value Stream Analytics standalone settings page

Rudy Crespo requested to merge 381002-vsa-settings-page-add-form-content into master

What does this MR do and why?

As part of the migration of the create/edit form from the modal within the Value Stream Analytics to a standalone settings page, this MR copies over the value_stream_form_content component and its spec from the main VSA app to the new VSA settings app and modifies it to meet the latter's requirements. In doing so, both the edit and new views for the VSA settings app now have functioning forms.

This is the second of several merge requests to resolve #381002 (closed).

Task Status
Create new Vue app and entry points at group level for standalone VSA settings page ( !146538 (merged))
Migrate value_stream_form_content to VSASettings app.
Link to settings pages from the main Value Stream Analytics application. 🚧

MR acceptance checklist

Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Screenshots or screen recordings

Screenshots are required for UI changes, and strongly recommended for all other merge requests.

View Before After
New Value Stream Screenshot 2024-03-04 at 2.38.07 PM.png Screenshot 2024-03-19 at 1.52.58 PM.png
Edit Value Stream Screenshot 2024-03-04 at 2.38.16 PM.png Screenshot 2024-03-19 at 1.53.06 PM.png

How to set up and validate locally

Numbered steps to set up and validate the change are strongly suggested.

  1. Please make sure that you're on the GitLab Ultimate plan.
  2. Feature.enable(:vsa_standalone_settings_page)
  3. Visit any group that has VSA data or follow these steps:
    1. run FILTER=cycle_analytics SEED_VSA=1 bundle exec rake db:seed_fu
    2. Visit the group's VSA. The group URL is present in the output of the command above.
  4. Navigate to http://{YOUR_LOCAL_INSTANCE}/groups/{GROUP_PATH}/-/analytics/value_stream_analytics/value_streams/new and verify that the form works correctly – upon submission, it should redirect to the new value stream page.
  5. Grab the ID of any value stream from your chosen VSA from the value of the query parameter value_stream_id and navigate to http://{YOUR_LOCAL_INSTANCE}/groups/{GROUP_PATH}/-/analytics/value_stream_analytics/value_streams/{VALUE_STREAM_ID}/edit. Verify that the form works correctly – upon submission, the value stream is successfully modified and it should not redirect.

Related to #381002 (closed)

Diffs

value_stream_form_content diff

Click to expand
diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_form_content.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content.vue
index 9a778438f05f..18e7857b85bd 100644
--- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_form_content.vue
+++ b/ee/app/assets/javascripts/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content.vue
@@ -1,14 +1,5 @@
 <script>
-import {
-  GlAlert,
-  GlButton,
-  GlForm,
-  GlFormInput,
-  GlFormGroup,
-  GlFormRadioGroup,
-  GlLoadingIcon,
-  GlModal,
-} from '@gitlab/ui';
+import { GlAlert, GlButton, GlForm, GlFormInput, GlFormGroup, GlFormRadioGroup } from '@gitlab/ui';
 import { cloneDeep, uniqueId } from 'lodash';
 import Vue from 'vue';
 // eslint-disable-next-line no-restricted-imports
@@ -17,22 +8,24 @@ import { filterStagesByHiddenStatus } from '~/analytics/cycle_analytics/utils';
 import { swapArrayItems } from '~/lib/utils/array_utility';
 import { sprintf } from '~/locale';
 import Tracking from '~/tracking';
+import { visitUrl } from '~/lib/utils/url_utility';
 import {
   STAGE_SORT_DIRECTION,
   i18n,
   defaultCustomStageFields,
   PRESET_OPTIONS,
   PRESET_OPTIONS_DEFAULT,
-} from './create_value_stream_form/constants';
-import CustomStageFields from './create_value_stream_form/custom_stage_fields.vue';
-import DefaultStageFields from './create_value_stream_form/default_stage_fields.vue';
+} from 'ee/analytics/cycle_analytics/components/create_value_stream_form/constants';
+import CustomStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue';
+import DefaultStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/default_stage_fields.vue';
 import {
   validateValueStreamName,
   cleanStageName,
   validateStage,
   formatStageDataForSubmission,
   hasDirtyStage,
-} from './create_value_stream_form/utils';
+} from 'ee/analytics/cycle_analytics/components/create_value_stream_form/utils';
+import ValueStreamFormContentHeader from './value_stream_form_content_header.vue';
 
 const initializeStageErrors = (defaultStageConfig, selectedPreset = PRESET_OPTIONS_DEFAULT) =>
   selectedPreset === PRESET_OPTIONS_DEFAULT ? defaultStageConfig.map(() => ({})) : [{}];
@@ -42,7 +35,6 @@ const initializeStages = (defaultStageConfig, selectedPreset = PRESET_OPTIONS_DE
     selectedPreset === PRESET_OPTIONS_DEFAULT
       ? defaultStageConfig
       : [{ ...defaultCustomStageFields }];
-
   return stages.map((stage) => ({ ...stage, transitionKey: uniqueId('stage-') }));
 };
 
@@ -61,10 +53,9 @@ export default {
     GlFormInput,
     GlFormGroup,
     GlFormRadioGroup,
-    GlLoadingIcon,
-    GlModal,
     DefaultStageFields,
     CustomStageFields,
+    ValueStreamFormContentHeader,
   },
   mixins: [Tracking.mixin()],
   props: {
@@ -92,6 +83,11 @@ export default {
       required: false,
       default: false,
     },
+    valueStreamPath: {
+      type: String,
+      required: false,
+      default: null,
+    },
   },
   data() {
     const {
@@ -117,12 +113,14 @@ export default {
       nameErrors,
       stageErrors,
       showSubmitError: false,
+      isRedirecting: false,
       ...additionalFields,
     };
   },
   computed: {
     ...mapState({
       isCreating: 'isCreatingValueStream',
+      isSaving: 'isEditingValueStream',
       isFetchingGroupLabels: 'isFetchingGroupLabels',
       formEvents: 'formEvents',
       defaultGroupLabels: 'defaultGroupLabels',
@@ -137,26 +135,8 @@ export default {
       const { initialFormErrors } = this;
       return Boolean(Object.keys(initialFormErrors).length);
     },
-    isLoading() {
-      return this.isCreating;
-    },
-    formTitle() {
-      return this.isEditing ? this.$options.i18n.EDIT_FORM_TITLE : this.$options.i18n.FORM_TITLE;
-    },
-    primaryProps() {
-      return {
-        text: this.isEditing ? this.$options.i18n.EDIT_FORM_ACTION : this.$options.i18n.FORM_TITLE,
-        attributes: { variant: 'confirm', loading: this.isLoading },
-      };
-    },
-    secondaryProps() {
-      return {
-        text: this.$options.i18n.BTN_ADD_ANOTHER_STAGE,
-        attributes: { category: 'secondary', variant: 'confirm', class: '' },
-      };
-    },
-    cancelProps() {
-      return { text: this.$options.i18n.BTN_CANCEL };
+    isSubmitting() {
+      return this.isCreating || this.isSaving;
     },
     hasFormErrors() {
       return Boolean(
@@ -177,13 +157,8 @@ export default {
       return this.stages.map(({ name }) => cleanStageName(name));
     },
   },
-  created() {
-    if (!this.defaultGroupLabels) {
-      this.fetchGroupLabels();
-    }
-  },
   methods: {
-    ...mapActions(['createValueStream', 'updateValueStream', 'fetchGroupLabels']),
+    ...mapActions(['createValueStream', 'updateValueStream']),
     onSubmit() {
       this.showSubmitError = false;
       this.validate();
@@ -203,24 +178,31 @@ export default {
       }
 
       return req(params).then(() => {
-        if (!this.hasInitialFormErrors) {
-          const msg = this.isEditing
-            ? this.$options.i18n.FORM_EDITED
-            : this.$options.i18n.FORM_CREATED;
-          this.$toast.show(sprintf(msg, { name: this.name }));
-          this.name = '';
-          this.nameErrors = [];
-          this.stages = initializeStages(this.defaultStageConfig, this.selectedPreset);
-          this.stageErrors = initializeStageErrors(this.defaultStageConfig, this.selectedPreset);
-          this.track('submit_form', {
-            label: this.isEditing ? 'edit_value_stream' : 'create_value_stream',
-          });
-        } else {
+        if (this.hasInitialFormErrors) {
           const { name: nameErrors = [], stages: stageErrors = [{}] } = this.initialFormErrors;
 
+          this.isRedirecting = false;
           this.nameErrors = nameErrors;
           this.stageErrors = stageErrors;
           this.showSubmitError = true;
+
+          return;
+        }
+
+        const msg = this.isEditing
+          ? this.$options.i18n.FORM_EDITED
+          : this.$options.i18n.FORM_CREATED;
+        this.$toast.show(sprintf(msg, { name: this.name }));
+        this.nameErrors = [];
+        this.stageErrors = initializeStageErrors(this.defaultStageConfig, this.selectedPreset);
+        this.track('submit_form', {
+          label: this.isEditing ? 'edit_value_stream' : 'create_value_stream',
+        });
+
+        if (!this.isEditing && this.valueStreamPath) {
+          this.isRedirecting = true;
+
+          visitUrl(this.valueStreamPath);
         }
       });
     },
@@ -342,30 +324,23 @@ export default {
 };
 </script>
 <template>
-  <gl-modal
-    data-testid="value-stream-form-modal"
-    modal-id="value-stream-form-modal"
-    dialog-class="gl-align-items-flex-start! gl-py-7"
-    scrollable
-    :title="formTitle"
-    :action-primary="primaryProps"
-    :action-secondary="secondaryProps"
-    :action-cancel="cancelProps"
-    :hide-footer="isFetchingGroupLabels"
-    @hidden.prevent="$emit('hidden')"
-    @secondary.prevent="onAddStage"
-    @primary.prevent="onSubmit"
-  >
-    <gl-loading-icon v-if="isFetchingGroupLabels" size="lg" color="dark" class="gl-my-12" />
-    <gl-form v-else>
-      <gl-alert
-        v-if="showSubmitError"
-        variant="danger"
-        class="gl-mb-3"
-        @dismiss="showSubmitError = false"
-      >
-        {{ $options.i18n.SUBMIT_FAILED }}
-      </gl-alert>
+  <div>
+    <gl-alert
+      v-if="showSubmitError"
+      variant="danger"
+      class="gl-mb-3"
+      @dismiss="showSubmitError = false"
+    >
+      {{ $options.i18n.SUBMIT_FAILED }}
+    </gl-alert>
+    <value-stream-form-content-header
+      class="gl-mb-6"
+      :is-editing="isEditing"
+      :is-loading="isSubmitting || isRedirecting"
+      :value-stream-path="valueStreamPath"
+      @clickedPrimaryAction="onSubmit"
+    />
+    <gl-form>
       <gl-form-group
         data-testid="create-value-stream-name"
         label-for="create-value-stream-name"
@@ -411,7 +386,7 @@ export default {
             ref="formStages"
             :key="stage.id || stage.transitionKey"
           >
-            <hr class="gl-my-3" />
+            <hr class="gl-my-5" />
             <custom-stage-fields
               v-if="stage.custom"
               :stage-label="stageGroupLabel(activeStageIndex)"
@@ -420,6 +395,7 @@ export default {
               :index="activeStageIndex"
               :total-stages="stages.length"
               :errors="fieldErrors(activeStageIndex)"
+              :default-group-labels="defaultGroupLabels"
               @move="handleMove"
               @remove="onRemove"
               @input="onFieldInput(activeStageIndex, $event)"
@@ -438,6 +414,17 @@ export default {
             />
           </div>
         </transition-group>
+        <div>
+          <hr class="gl-mt-2 gl-mb-5" />
+          <gl-button
+            data-testid="vsa-add-stage-button"
+            category="secondary"
+            variant="confirm"
+            icon="plus"
+            @click="onAddStage"
+            >{{ $options.i18n.BTN_ADD_ANOTHER_STAGE }}</gl-button
+          >
+        </div>
         <div v-if="hiddenStages.length">
           <hr />
           <gl-form-group
@@ -458,5 +445,5 @@ export default {
         </div>
       </div>
     </gl-form>
-  </gl-modal>
+  </div>
 </template>

value_stream_form_content_spec diff

Click to expand
diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_content_spec.js b/ee/spec/frontend/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content_spec.js
index 90f7eafe322b..bf6715682d3d 100644
--- a/ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_content_spec.js
+++ b/ee/spec/frontend/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content_spec.js
@@ -1,29 +1,35 @@
-import { GlAlert, GlModal, GlFormInput } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlAlert, GlFormInput } from '@gitlab/ui';
 import Vue from 'vue';
 // eslint-disable-next-line no-restricted-imports
 import Vuex from 'vuex';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import {
-  i18n,
   PRESET_OPTIONS_BLANK,
   PRESET_OPTIONS_DEFAULT,
 } from 'ee/analytics/cycle_analytics/components/create_value_stream_form/constants';
 import CustomStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue';
 import CustomStageEventField from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_event_field.vue';
 import DefaultStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/default_stage_fields.vue';
-import ValueStreamFormContent from 'ee/analytics/cycle_analytics/components/value_stream_form_content.vue';
+import ValueStreamFormContent from 'ee/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content.vue';
 import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { visitUrl } from '~/lib/utils/url_utility';
 import {
   convertObjectPropsToCamelCase,
   convertObjectPropsToSnakeCase,
 } from '~/lib/utils/common_utils';
+import ValueStreamFormContentHeader from 'ee/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content_header.vue';
 import {
   customStageEvents as formEvents,
   defaultStageConfig,
   rawCustomStage,
   groupLabels as defaultGroupLabels,
-} from '../mock_data';
+  valueStreamPath,
+} from '../../mock_data';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+  ...jest.requireActual('~/lib/utils/url_utility'),
+  visitUrl: jest.fn(),
+}));
 
 const scrollIntoViewMock = jest.fn();
 HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
@@ -36,8 +42,6 @@ describe('ValueStreamFormContent', () => {
 
   const createValueStreamMock = jest.fn(() => Promise.resolve());
   const updateValueStreamMock = jest.fn(() => Promise.resolve());
-  const fetchGroupLabelsMock = jest.fn(() => Promise.resolve());
-  const mockEvent = { preventDefault: jest.fn() };
   const mockToastShow = jest.fn();
   const streamName = 'Cool stream';
   const initialFormNameErrors = { name: ['Name field required'] };
@@ -70,6 +74,7 @@ describe('ValueStreamFormContent', () => {
     new Vuex.Store({
       state: {
         isCreatingValueStream: false,
+        isEditingValueStream: false,
         formEvents,
         defaultGroupLabels,
         ...state,
@@ -77,35 +82,33 @@ describe('ValueStreamFormContent', () => {
       actions: {
         createValueStream: createValueStreamMock,
         updateValueStream: updateValueStreamMock,
-        fetchGroupLabels: fetchGroupLabelsMock,
       },
     });
 
   const createComponent = ({ props = {}, data = {}, stubs = {}, state = {} } = {}) =>
-    extendedWrapper(
-      shallowMount(ValueStreamFormContent, {
-        store: fakeStore({ state }),
-        data() {
-          return {
-            ...data,
-          };
-        },
-        propsData: {
-          defaultStageConfig,
-          ...props,
-        },
-        mocks: {
-          $toast: {
-            show: mockToastShow,
-          },
-        },
-        stubs: {
-          ...stubs,
+    shallowMountExtended(ValueStreamFormContent, {
+      store: fakeStore({ state }),
+      data() {
+        return {
+          ...data,
+        };
+      },
+      propsData: {
+        defaultStageConfig,
+        valueStreamPath,
+        ...props,
+      },
+      mocks: {
+        $toast: {
+          show: mockToastShow,
         },
-      }),
-    );
+      },
+      stubs: {
+        ...stubs,
+      },
+    });
 
-  const findModal = () => wrapper.findComponent(GlModal);
+  const findFormHeader = () => wrapper.findComponent(ValueStreamFormContentHeader);
   const findExtendedFormFields = () => wrapper.findByTestId('extended-form-fields');
   const findDefaultStages = () => findExtendedFormFields().findAllComponents(DefaultStageFields);
   const findCustomStages = () => findExtendedFormFields().findAllComponents(CustomStageFields);
@@ -113,7 +116,7 @@ describe('ValueStreamFormContent', () => {
   const findRestoreButton = () => wrapper.findByTestId('vsa-reset-button');
   const findRestoreStageButton = (index) => wrapper.findByTestId(`stage-action-restore-${index}`);
   const findHiddenStages = () => wrapper.findAllByTestId('vsa-hidden-stage').wrappers;
-  const findBtn = (btn) => findModal().props(btn);
+  const findAddStageButton = () => wrapper.findByTestId('vsa-add-stage-button');
   const findCustomStageEventField = (index = 0) =>
     wrapper.findAllComponents(CustomStageEventField).at(index);
   const findFieldErrors = (testId) => wrapper.findByTestId(testId).attributes('invalid-feedback');
@@ -124,8 +127,8 @@ describe('ValueStreamFormContent', () => {
   const fillStageNameAtIndex = (name, index) =>
     findCustomStages().at(index).findComponent(GlFormInput).vm.$emit('input', name);
 
-  const clickSubmit = () => findModal().vm.$emit('primary', mockEvent);
-  const clickAddStage = () => findModal().vm.$emit('secondary', mockEvent);
+  const clickSubmit = () => findFormHeader().vm.$emit('clickedPrimaryAction');
+  const clickAddStage = () => findAddStageButton().vm.$emit('click');
   const clickRestoreStageAtIndex = (index) => findRestoreStageButton(index).vm.$emit('click');
   const expectFieldError = (testId, error = '') => expect(findFieldErrors(testId)).toBe(error);
   const expectCustomFieldError = (index, attr, error = '') =>
@@ -138,16 +141,16 @@ describe('ValueStreamFormContent', () => {
       wrapper = createComponent({ state: { defaultGroupLabels: null } });
     });
 
-    it('has the extended fields', () => {
-      expect(findExtendedFormFields().exists()).toBe(true);
-    });
-
-    it('sets the submit action text to "Create Value Stream"', () => {
-      expect(findBtn('actionPrimary').text).toBe(i18n.FORM_TITLE);
+    it('has the form header', () => {
+      expect(findFormHeader().props()).toMatchObject({
+        isLoading: false,
+        isEditing: false,
+        valueStreamPath,
+      });
     });
 
-    it('renders the modal footer buttons', () => {
-      expect(findModal().attributes('hide-footer')).toBeUndefined();
+    it('has the extended fields', () => {
+      expect(findExtendedFormFields().exists()).toBe(true);
     });
 
     describe('Preset selector', () => {
@@ -197,10 +200,6 @@ describe('ValueStreamFormContent', () => {
       it('does not display any hidden stages', () => {
         expect(findHiddenStages()).toHaveLength(0);
       });
-
-      it('will fetch group labels', () => {
-        expect(fetchGroupLabelsMock).toHaveBeenCalled();
-      });
     });
 
     describe('Add stage button', () => {
@@ -213,7 +212,7 @@ describe('ValueStreamFormContent', () => {
       });
 
       it('has the add stage button', () => {
-        expect(findBtn('actionSecondary')).toMatchObject({ text: 'Add another stage' });
+        expect(findAddStageButton().exists()).toBe(true);
       });
 
       it('adds a blank custom stage when clicked', async () => {
@@ -324,6 +323,20 @@ describe('ValueStreamFormContent', () => {
         unmockTracking();
       });
 
+      describe('form submitting', () => {
+        beforeEach(() => {
+          wrapper = createComponent({
+            state: {
+              isCreatingValueStream: true,
+            },
+          });
+        });
+
+        it("enables form header's loading state", () => {
+          expect(findFormHeader().props('isLoading')).toBe(true);
+        });
+      });
+
       describe('form submitted successfully', () => {
         beforeEach(async () => {
           wrapper = createComponent();
@@ -352,10 +365,6 @@ describe('ValueStreamFormContent', () => {
           });
         });
 
-        it('clears the name field', () => {
-          expect(findNameInput().attributes('value')).toBe('');
-        });
-
         it('displays a toast message', () => {
           expect(mockToastShow).toHaveBeenCalledWith(`'${streamName}' Value Stream created`);
         });
@@ -365,6 +374,14 @@ describe('ValueStreamFormContent', () => {
             label: 'create_value_stream',
           });
         });
+
+        it('form header should be in loading state', () => {
+          expect(findFormHeader().props('isLoading')).toBe(true);
+        });
+
+        it('redirects to the new value stream page', () => {
+          expect(visitUrl).toHaveBeenCalledWith(valueStreamPath);
+        });
       });
 
       describe('form submission fails', () => {
@@ -394,6 +411,14 @@ describe('ValueStreamFormContent', () => {
           expect(mockToastShow).not.toHaveBeenCalled();
         });
 
+        it('does not redirect to the new value stream page', () => {
+          expect(visitUrl).not.toHaveBeenCalled();
+        });
+
+        it('form header should not be in loading state', () => {
+          expect(findFormHeader().props('isLoading')).toBe(false);
+        });
+
         it('renders errors for the name field', () => {
           expectFieldError('create-value-stream-name', formSubmissionErrors.name[0]);
         });
@@ -423,8 +448,8 @@ describe('ValueStreamFormContent', () => {
       expect(findPresetSelector().exists()).toBe(false);
     });
 
-    it('sets the submit action text to "Save value stream"', () => {
-      expect(findBtn('actionPrimary').text).toBe(i18n.EDIT_FORM_ACTION);
+    it("enables form header's editing state", () => {
+      expect(findFormHeader().props('isEditing')).toBe(true);
     });
 
     it('does not display any hidden stages', () => {
@@ -518,7 +543,7 @@ describe('ValueStreamFormContent', () => {
       });
 
       it('has the add stage button', () => {
-        expect(findBtn('actionSecondary')).toMatchObject({ text: i18n.BTN_ADD_ANOTHER_STAGE });
+        expect(findAddStageButton().exists()).toBe(true);
       });
 
       it('adds a blank custom stage when clicked', async () => {
@@ -549,6 +574,25 @@ describe('ValueStreamFormContent', () => {
         unmockTracking();
       });
 
+      describe('form submitting', () => {
+        beforeEach(() => {
+          wrapper = createComponent({
+            props: {
+              initialPreset,
+              initialData,
+              isEditing: true,
+            },
+            state: {
+              isEditingValueStream: true,
+            },
+          });
+        });
+
+        it("enables form header's loading state", () => {
+          expect(findFormHeader().props('isLoading')).toBe(true);
+        });
+      });
+
       describe('form submitted successfully', () => {
         beforeEach(() => {
           wrapper = createComponent({
@@ -580,6 +624,14 @@ describe('ValueStreamFormContent', () => {
             label: 'edit_value_stream',
           });
         });
+
+        it('does not redirect to the value stream page', () => {
+          expect(visitUrl).not.toHaveBeenCalled();
+        });
+
+        it('form header should not be in loading state', () => {
+          expect(findFormHeader().props('isLoading')).toBe(false);
+        });
       });
 
       describe('form submission fails', () => {
@@ -613,6 +665,14 @@ describe('ValueStreamFormContent', () => {
           expect(mockToastShow).not.toHaveBeenCalled();
         });
 
+        it('does not redirect to the value stream page', () => {
+          expect(visitUrl).not.toHaveBeenCalled();
+        });
+
+        it('form header should not be in loading state', () => {
+          expect(findFormHeader().props('isLoading')).toBe(false);
+        });
+
         it('renders errors for the name field', () => {
           expectFieldError('create-value-stream-name', formSubmissionErrors.name[0]);
         });
@@ -629,19 +689,4 @@ describe('ValueStreamFormContent', () => {
       });
     });
   });
-
-  describe('isFetchingGroupLabels=true', () => {
-    beforeEach(() => {
-      wrapper = createComponent({
-        state: {
-          defaultGroupLabels: [],
-          isFetchingGroupLabels: true,
-        },
-      });
-    });
-
-    it('hides the modal footer buttons', () => {
-      expect(findModal().attributes('hide-footer')).toBe('true');
-    });
-  });
 });
Edited by Rudy Crespo

Merge request reports