Commit afa35fc4 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by David O'Regan

Adds some simple transitions for changing ui

Minor reorganization of some of the UI elements
to limit jumpiness in transitions
parent 97fbd3ee
......@@ -140,20 +140,23 @@ export default {
>
</gl-dropdown>
</gl-form-group>
<gl-form-group
v-if="startEventRequiresLabel"
class="gl-w-half gl-ml-2"
:data-testid="`custom-stage-start-event-label-${index}`"
:label="$options.i18n.FORM_FIELD_START_EVENT_LABEL"
:state="hasFieldErrors('startEventLabelId')"
:invalid-feedback="fieldErrorMessage('startEventLabelId')"
>
<labels-selector
:selected-label-id="[stage.startEventLabelId]"
:name="`custom-stage-start-label-${index}`"
@select-label="$emit('input', { field: 'startEventLabelId', value: $event })"
/>
</gl-form-group>
<div class="gl-w-half gl-ml-2">
<transition name="fade">
<gl-form-group
v-if="startEventRequiresLabel"
:data-testid="`custom-stage-start-event-label-${index}`"
:label="$options.i18n.FORM_FIELD_START_EVENT_LABEL"
:state="hasFieldErrors('startEventLabelId')"
:invalid-feedback="fieldErrorMessage('startEventLabelId')"
>
<labels-selector
:selected-label-id="[stage.startEventLabelId]"
:name="`custom-stage-start-label-${index}`"
@select-label="$emit('input', { field: 'startEventLabelId', value: $event })"
/>
</gl-form-group>
</transition>
</div>
</div>
<div class="gl-display-flex gl-justify-content-between">
<gl-form-group
......@@ -180,20 +183,23 @@ export default {
>
</gl-dropdown>
</gl-form-group>
<gl-form-group
v-if="endEventRequiresLabel"
class="gl-w-half gl-ml-2"
:data-testid="`custom-stage-end-event-label-${index}`"
:label="$options.i18n.FORM_FIELD_END_EVENT_LABEL"
:state="hasFieldErrors('endEventLabelId')"
:invalid-feedback="fieldErrorMessage('endEventLabelId')"
>
<labels-selector
:selected-label-id="[stage.endEventLabelId]"
:name="`custom-stage-end-label-${index}`"
@select-label="$emit('input', { field: 'endEventLabelId', value: $event })"
/>
</gl-form-group>
<div class="gl-w-half gl-ml-2">
<transition name="fade">
<gl-form-group
v-if="endEventRequiresLabel"
:data-testid="`custom-stage-end-event-label-${index}`"
:label="$options.i18n.FORM_FIELD_END_EVENT_LABEL"
:state="hasFieldErrors('endEventLabelId')"
:invalid-feedback="fieldErrorMessage('endEventLabelId')"
>
<labels-selector
:selected-label-id="[stage.endEventLabelId]"
:name="`custom-stage-end-label-${index}`"
@select-label="$emit('input', { field: 'endEventLabelId', value: $event })"
/>
</gl-form-group>
</transition>
</div>
</div>
</div>
</template>
......@@ -86,25 +86,23 @@ export default {
/>
</div>
<div
class="gl-display-flex gl-align-items-center gl-mt-2"
class="gl-display-flex gl-align-items-center gl-mt-3"
:data-testid="`stage-start-event-${index}`"
>
<span class="gl-m-0 gl-vertical-align-middle gl-mr-2 gl-font-weight-bold">{{
$options.i18n.DEFAULT_FIELD_START_EVENT_LABEL
}}</span>
<gl-form-text class="gl-m-0">{{ eventName(stage.startEventIdentifier) }}</gl-form-text>
<gl-form-text v-if="stage.startEventLabel" class="gl-m-0"
>&nbsp;-&nbsp;{{ stage.startEventLabel }}</gl-form-text
>
<gl-form-text class="gl-m-0" tag="span">{{
eventName(stage.startEventIdentifier)
}}</gl-form-text>
</div>
<div class="gl-display-flex gl-align-items-center" :data-testid="`stage-end-event-${index}`">
<span class="gl-m-0 gl-vertical-align-middle gl-mr-2 gl-font-weight-bold">{{
$options.i18n.DEFAULT_FIELD_END_EVENT_LABEL
}}</span>
<gl-form-text class="gl-m-0">{{ eventName(stage.endEventIdentifier) }}</gl-form-text>
<gl-form-text v-if="stage.endEventLabel" class="gl-m-0"
>&nbsp;-&nbsp;{{ stage.endEventLabel }}</gl-form-text
>
<gl-form-text class="gl-m-0" tag="span">{{
eventName(stage.endEventIdentifier)
}}</gl-form-text>
</div>
</div>
</template>
......@@ -44,7 +44,7 @@ export default {
return this.canRemove ? __('Remove') : __('Hide');
},
hideActionIcon() {
return this.canRemove ? 'remove' : 'archive';
return this.canRemove ? 'remove' : 'eye-slash';
},
hideActionTestId() {
return `stage-action-${this.canRemove ? 'remove' : 'hide'}-${this.index}`;
......
......@@ -243,12 +243,26 @@ export default {
]);
Vue.set(this, 'stages', [...this.stages, target]);
},
onAddStage() {
lastStage() {
const stages = this.$refs.formStages;
return stages[stages.length - 1];
},
async scrollToLastStage() {
await this.$nextTick();
// Scroll to the new stage we have added
this.lastStage().focus();
this.lastStage().scrollIntoView({ behavior: 'smooth' });
},
addNewStage() {
// validate previous stages only and add a new stage
this.validate();
Vue.set(this, 'stages', [...this.stages, { ...defaultCustomStageFields }]);
Vue.set(this, 'stageErrors', [...this.stageErrors, {}]);
},
onAddStage() {
this.addNewStage();
this.scrollToLastStage();
},
onFieldInput(activeStageIndex, { field, value }) {
const updatedStage = { ...this.stages[activeStageIndex], [field]: value };
Vue.set(this.stages, activeStageIndex, updatedStage);
......@@ -320,13 +334,15 @@ export default {
:state="isValueStreamNameValid"
required
/>
<gl-button
v-if="canRestore"
class="gl-ml-3"
variant="link"
@click="handleResetDefaults"
>{{ $options.i18n.RESTORE_DEFAULTS }}</gl-button
>
<transition name="fade">
<gl-button
v-if="canRestore"
class="gl-ml-3"
variant="link"
@click="handleResetDefaults"
>{{ $options.i18n.RESTORE_DEFAULTS }}</gl-button
>
</transition>
</div>
</gl-form-group>
<gl-form-radio-group
......@@ -339,7 +355,11 @@ export default {
@input="onSelectPreset"
/>
<div v-if="hasExtendedFormFields" data-testid="extended-form-fields">
<div v-for="(stage, activeStageIndex) in stages" :key="stageKey(activeStageIndex)">
<div
v-for="(stage, activeStageIndex) in stages"
ref="formStages"
:key="stageKey(activeStageIndex)"
>
<hr class="gl-my-3" />
<span
class="gl-display-flex gl-m-0 gl-vertical-align-middle gl-mr-2 gl-font-weight-bold gl-display-flex gl-pb-3"
......
import { GlFormGroup } from '@gitlab/ui';
import { GlFormGroup, GlFormInput, GlFormText } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DefaultStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/default_stage_fields.vue';
import StageFieldActions from 'ee/analytics/cycle_analytics/components/create_value_stream_form/stage_field_actions.vue';
......@@ -37,8 +37,11 @@ describe('DefaultStageFields', () => {
}
const findStageFieldName = () => wrapper.find('[name="create-value-stream-stage-0"]');
const findStageFieldNameInput = () => findStageFieldName().find(GlFormInput);
const findStartEvent = () => wrapper.find('[data-testid="stage-start-event-0"]');
const findStartEventInput = () => findStartEvent().find(GlFormText);
const findEndEvent = () => wrapper.find('[data-testid="stage-end-event-0"]');
const findEndEventInput = () => findEndEvent().find(GlFormText);
const findFormGroup = () => wrapper.find(GlFormGroup);
const findFieldActions = () => wrapper.find(StageFieldActions);
......@@ -52,28 +55,21 @@ describe('DefaultStageFields', () => {
});
it('renders the stage field name', () => {
expect(findStageFieldName().exists()).toBe(true);
expect(findStageFieldName().html()).toContain(defaultStage.name);
expect(findStageFieldNameInput().exists()).toBe(true);
expect(findStageFieldNameInput().html()).toContain(defaultStage.name);
});
it('disables input for the stage field name', () => {
expect(findStageFieldName().attributes('disabled')).toBe('disabled');
expect(findStageFieldNameInput().attributes('disabled')).toBe('disabled');
});
it('renders the field start event', () => {
expect(findStartEvent().exists()).toBe(true);
expect(findStartEvent().html()).toContain(ISSUE_CREATED.name);
expect(findStartEventInput().exists()).toBe(true);
expect(findStartEventInput().text()).toBe(ISSUE_CREATED.name);
});
it('renders the field end event', () => {
const content = findEndEvent().html();
expect(content).toContain(ISSUE_CLOSED.name);
expect(content).toContain(defaultStage.endEventLabel);
});
it('renders an event label if it exists', () => {
const content = findEndEvent().html();
expect(content).toContain(defaultStage.endEventLabel);
expect(findEndEventInput().text()).toBe(ISSUE_CLOSED.name);
});
it('does not emits any input', () => {
......@@ -107,7 +103,7 @@ describe('DefaultStageFields', () => {
});
it('displays the field error', () => {
expect(findFormGroup().html()).toContain(stageNameError);
expect(findFormGroup().attributes('invalid-feedback')).toBe(stageNameError);
});
});
});
......@@ -12,6 +12,9 @@ import {
} from '~/lib/utils/common_utils';
import { customStageEvents as formEvents, defaultStageConfig, rawCustomStage } from '../mock_data';
const scrollIntoViewMock = jest.fn();
HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -144,22 +147,31 @@ describe('ValueStreamForm', () => {
});
describe('Add stage button', () => {
beforeEach(() => {
wrapper = createComponent({
props: { hasExtendedFormFields: true },
stubs: {
CustomStageFields,
},
});
});
it('has the add stage button', () => {
expect(findBtn('actionSecondary')).toMatchObject({ text: 'Add another stage' });
});
it('adds a blank custom stage when clicked', () => {
expect(wrapper.vm.stages.length).toBe(defaultStageConfig.length);
it('adds a blank custom stage when clicked', async () => {
expect(wrapper.vm.stages).toHaveLength(defaultStageConfig.length);
clickAddStage();
await clickAddStage();
expect(wrapper.vm.stages.length).toBe(defaultStageConfig.length + 1);
});
it('validates existing fields when clicked', () => {
expect(wrapper.vm.nameError).toEqual([]);
it('validates existing fields when clicked', async () => {
expect(wrapper.vm.nameError).toHaveLength(0);
clickAddStage();
await clickAddStage();
expect(wrapper.vm.nameError).toEqual(['Name is required']);
});
......@@ -225,24 +237,37 @@ describe('ValueStreamForm', () => {
});
describe('Add stage button', () => {
beforeEach(() => {
wrapper = createComponent({
props: {
initialPreset,
initialData,
isEditing: true,
hasExtendedFormFields: true,
},
stubs: {
CustomStageFields,
},
});
});
it('has the add stage button', () => {
expect(findBtn('actionSecondary')).toMatchObject({ text: 'Add another stage' });
});
it('adds a blank custom stage when clicked', () => {
it('adds a blank custom stage when clicked', async () => {
expect(wrapper.vm.stages.length).toBe(stageCount);
clickAddStage();
await clickAddStage();
expect(wrapper.vm.stages.length).toBe(stageCount + 1);
});
it('validates existing fields when clicked', () => {
it('validates existing fields when clicked', async () => {
expect(wrapper.vm.nameError).toEqual([]);
wrapper.findByTestId('create-value-stream-name').find(GlFormInput).vm.$emit('input', '');
clickAddStage();
await clickAddStage();
expect(wrapper.vm.nameError).toEqual(['Name is required']);
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment