Commit 23969ec0 authored by Mark Florian's avatar Mark Florian Committed by Nicolò Maria Mezzopera

Add FormInput component

This adds a form input component, to be used in the [SAST Configuration
UI][epic].

This component is a fairly thin wrapper around the `GlFormGroup` and
`GlFormInput` components, to conform to the [design]. It will be
consumed by a [parent container component][container], itself driven via
a schema to generate a form.

Addresses https://gitlab.com/gitlab-org/gitlab/-/issues/225224.

[epic]: https://gitlab.com/groups/gitlab-org/-/epics/3659
[container]: https://gitlab.com/gitlab-org/gitlab/-/issues/231370
[design]: https://gitlab.com/gitlab-org/gitlab/-/issues/228830
parent e1253828
import { s__ } from '~/locale';
export const SMALL = 'small';
export const MEDIUM = 'medium';
export const LARGE = 'large';
// The backend will supply sizes matching the keys of this map; the values
// correspond to values acceptable to the underlying components' size props.
export const SCHEMA_TO_PROP_SIZE_MAP = {
[SMALL]: 'xs',
[MEDIUM]: 'md',
[LARGE]: 'xl',
};
export const CUSTOM_VALUE_MESSAGE = s__(
"SecurityConfiguration|Using custom settings. You won't receive automatic updates on this variable. %{anchorStart}Restore to default%{anchorEnd}",
);
<script>
import { GlFormGroup, GlFormInput, GlFormText, GlLink, GlSprintf } from '@gitlab/ui';
import { CUSTOM_VALUE_MESSAGE, SCHEMA_TO_PROP_SIZE_MAP, LARGE } from './constants';
export default {
components: {
GlFormGroup,
GlFormInput,
GlFormText,
GlLink,
GlSprintf,
},
model: {
prop: 'value',
event: 'input',
},
props: {
value: {
type: String,
required: true,
},
defaultValue: {
type: String,
required: true,
},
field: {
type: String,
required: true,
},
size: {
type: String,
required: false,
default: LARGE,
validator: size => Object.keys(SCHEMA_TO_PROP_SIZE_MAP).includes(size),
},
label: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
},
computed: {
isCustomValue() {
return this.value !== this.defaultValue;
},
inputSize() {
return SCHEMA_TO_PROP_SIZE_MAP[this.size];
},
},
methods: {
resetToDefaultValue() {
this.$emit('input', this.defaultValue);
},
},
i18n: {
CUSTOM_VALUE_MESSAGE,
},
};
</script>
<template>
<gl-form-group :label-for="field">
<template #label>
{{ label }}
<gl-form-text class="gl-mt-3">{{ description }}</gl-form-text>
</template>
<gl-form-input :id="field" :size="inputSize" :value="value" @input="$emit('input', $event)" />
<template v-if="isCustomValue" #description>
<gl-sprintf :message="$options.i18n.CUSTOM_VALUE_MESSAGE">
<template #anchor="{ content }">
<gl-link @click="resetToDefaultValue" v-text="content" />
</template>
</gl-sprintf>
</template>
</gl-form-group>
</template>
import { mount } from '@vue/test-utils';
import { GlFormInput, GlLink } from '@gitlab/ui';
import FormInput from 'ee/security_configuration/sast/components/form_input.vue';
import { SCHEMA_TO_PROP_SIZE_MAP } from 'ee/security_configuration/sast/components/constants';
describe('FormInput component', () => {
let wrapper;
const testProps = {
field: 'field',
label: 'label',
description: 'description',
defaultValue: 'defaultValue',
value: 'defaultValue',
};
const newValue = 'foo';
const createComponent = ({ props = {} } = {}) => {
wrapper = mount(FormInput, {
propsData: {
...props,
},
});
};
const findInput = () => wrapper.find('input[type="text"]');
const findLabel = () => wrapper.find('label');
const findInputComponent = () => wrapper.find(GlFormInput);
const findRestoreDefaultLink = () => wrapper.find(GlLink);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('label', () => {
beforeEach(() => {
createComponent({
props: testProps,
});
});
it('renders the label', () => {
expect(findLabel().text()).toContain(testProps.label);
});
it('renders the description', () => {
expect(findLabel().text()).toContain(testProps.description);
});
});
describe('input', () => {
beforeEach(() => {
createComponent({
props: testProps,
});
});
it('sets the input to the value', () => {
expect(findInput().element.value).toBe(testProps.value);
});
it('is connected to the label', () => {
expect(findInput().attributes('id')).toBe(testProps.field);
expect(findLabel().attributes('for')).toBe(testProps.field);
});
describe('when the user changes the value', () => {
beforeEach(() => {
findInput().setValue(newValue);
});
it('emits an input event with the new value', () => {
expect(wrapper.emitted('input')).toEqual([[newValue]]);
});
});
});
describe('custom value message', () => {
describe('given the value equals the custom value', () => {
beforeEach(() => {
createComponent({
props: testProps,
});
});
it('does not display the custom value message', () => {
expect(findRestoreDefaultLink().exists()).toBe(false);
});
});
describe('given the value differs from the custom value', () => {
beforeEach(() => {
createComponent({
props: {
...testProps,
value: newValue,
},
});
});
it('displays the custom value message', () => {
expect(findRestoreDefaultLink().exists()).toBe(true);
});
describe('clicking on the restore default link', () => {
beforeEach(() => {
findRestoreDefaultLink().trigger('click');
});
it('emits an input event with the default value', () => {
expect(wrapper.emitted('input')).toEqual([[testProps.defaultValue]]);
});
});
});
});
describe('size prop', () => {
it.each(Object.entries(SCHEMA_TO_PROP_SIZE_MAP))(
'maps the %p size prop to %p',
(size, mappedSize) => {
createComponent({
props: {
...testProps,
size,
},
});
expect(findInputComponent().props('size')).toBe(mappedSize);
},
);
});
});
......@@ -21135,6 +21135,9 @@ msgstr ""
msgid "SecurityConfiguration|Testing & Compliance"
msgstr ""
msgid "SecurityConfiguration|Using custom settings. You won't receive automatic updates on this variable. %{anchorStart}Restore to default%{anchorEnd}"
msgstr ""
msgid "SecurityConfiguration|You can quickly enable all security scanning tools by enabling %{linkStart}Auto DevOps%{linkEnd}."
msgstr ""
......
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