diff --git a/app/assets/javascripts/groups/components/new_group_form.vue b/app/assets/javascripts/groups/components/new_group_form.vue index 60d99c5f531689e97145821a8bee830562951bc1..42ade46f11e4134ccb04e67c136405a7237e60e6 100644 --- a/app/assets/javascripts/groups/components/new_group_form.vue +++ b/app/assets/javascripts/groups/components/new_group_form.vue @@ -2,7 +2,8 @@ import { GlForm, GlFormFields, GlButton } from '@gitlab/ui'; import { formValidators } from '@gitlab/ui/dist/utils'; import { __, s__, sprintf } from '~/locale'; -import { FORM_FIELD_PATH } from '../constants'; +import { slugify } from '~/lib/utils/text_utility'; +import { FORM_FIELD_NAME, FORM_FIELD_PATH } from '../constants'; import GroupPathField from './group_path_field.vue'; export default { @@ -41,6 +42,7 @@ export default { hasPathBeenManuallySet: false, isPathLoading: false, formValues: { + [FORM_FIELD_NAME]: '', [FORM_FIELD_PATH]: '', }, }; @@ -48,6 +50,21 @@ export default { computed: { fields() { return { + [FORM_FIELD_NAME]: { + label: s__('Groups|Group name'), + validators: [ + formValidators.required(s__('Groups|Enter a descriptive name for your group.')), + ], + inputAttrs: { + width: { md: 'lg' }, + placeholder: __('My awesome group'), + }, + groupAttrs: { + description: s__( + 'Groups|Must start with letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses.', + ), + }, + }, [FORM_FIELD_PATH]: { label: s__('Groups|Group URL'), validators: [ @@ -76,6 +93,15 @@ export default { }; }, }, + watch: { + [`formValues.${FORM_FIELD_NAME}`](newName) { + if (this.hasPathBeenManuallySet) { + return; + } + + this.formValues[FORM_FIELD_PATH] = slugify(newName); + }, + }, methods: { onPathInput(event, formFieldsInputEvent) { formFieldsInputEvent(event); diff --git a/app/assets/javascripts/groups/constants.js b/app/assets/javascripts/groups/constants.js index 0e33686c129d66ad01cd9c07a7c34d49812254cb..d9ec00ab510741ba6f1458324803e5b783754adc 100644 --- a/app/assets/javascripts/groups/constants.js +++ b/app/assets/javascripts/groups/constants.js @@ -62,4 +62,5 @@ export const OVERVIEW_TABS_ARCHIVED_PROJECTS_SORTING_ITEMS = [ SORTING_ITEM_UPDATED, ]; +export const FORM_FIELD_NAME = 'name'; export const FORM_FIELD_PATH = 'path'; diff --git a/spec/frontend/groups/components/new_group_form_spec.js b/spec/frontend/groups/components/new_group_form_spec.js index 57891c5532ada03d49b6e8dd417c05573e32fc57..23dc0f3296780b8a2dd6b6f556fab6a4036c1f3e 100644 --- a/spec/frontend/groups/components/new_group_form_spec.js +++ b/spec/frontend/groups/components/new_group_form_spec.js @@ -27,6 +27,7 @@ describe('NewGroupForm', () => { }); }; + const findNameField = () => wrapper.findByLabelText('Group name'); const findPathField = () => wrapper.findComponent(GroupPathField); const setPathFieldValue = async (value) => { @@ -37,6 +38,12 @@ describe('NewGroupForm', () => { await wrapper.findByRole('button', { name: 'Create group' }).trigger('click'); }; + it('renders `Group name` field', () => { + createComponent(); + + expect(findNameField().exists()).toBe(true); + }); + it('renders `Group URL` field', () => { createComponent(); @@ -50,6 +57,7 @@ describe('NewGroupForm', () => { }); it('shows error message', () => { + expect(wrapper.findByText('Enter a descriptive name for your group.').exists()).toBe(true); expect(wrapper.findByText('Enter a path for your group.').exists()).toBe(true); expect(wrapper.emitted('submit')).toBeUndefined(); }); @@ -89,16 +97,41 @@ describe('NewGroupForm', () => { }); }); + describe('when `Group URL` has not been manually set', () => { + beforeEach(async () => { + createComponent(); + + await findNameField().setValue('Foo bar'); + }); + + it('sets `Group URL` when typing in `Group name`', () => { + expect(findPathField().props('value')).toBe('foo-bar'); + }); + }); + + describe('when `Group URL` has been manually set', () => { + beforeEach(async () => { + createComponent(); + + await setPathFieldValue('foo-bar-baz'); + await findNameField().setValue('Foo bar'); + }); + + it('does not modify `Group URL` when typing in `Group name`', () => { + expect(findPathField().props('value')).toBe('foo-bar-baz'); + }); + }); + describe('when form is submitted successfully', () => { beforeEach(async () => { createComponent(); - await setPathFieldValue('foo-bar'); + await findNameField().setValue('Foo bar'); await submitForm(); }); it('emits `submit` event with form values', () => { - expect(wrapper.emitted('submit')).toEqual([[{ path: 'foo-bar' }]]); + expect(wrapper.emitted('submit')).toEqual([[{ name: 'Foo bar', path: 'foo-bar' }]]); }); });