Commit 5ee882b4 authored by Zack Cuddy's avatar Zack Cuddy

Geo Form Validations

Currently the Geo Form does not respond
to form errors very intuitively.

This adds validations to the form for
name, url, and all the capcities.

This also disables the Save button
when an error is present.
parent f6697c96
<script>
import { mapActions } from 'vuex';
import { GlFormGroup, GlFormInput, GlFormCheckbox, GlDeprecatedButton } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import { GlFormGroup, GlFormInput, GlFormCheckbox, GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import GeoNodeFormCore from './geo_node_form_core.vue';
......@@ -13,7 +13,7 @@ export default {
GlFormGroup,
GlFormInput,
GlFormCheckbox,
GlDeprecatedButton,
GlButton,
GeoNodeFormCore,
GeoNodeFormSelectiveSync,
GeoNodeFormCapacities,
......@@ -53,6 +53,7 @@ export default {
};
},
computed: {
...mapGetters(['formHasError']),
saveButtonTitle() {
return this.node ? __('Update') : __('Save');
},
......@@ -118,16 +119,17 @@ export default {
</gl-form-group>
</section>
<section class="d-flex align-items-center mt-4">
<gl-deprecated-button
<gl-button
id="node-save-button"
data-qa-selector="add_node_button"
variant="success"
:disabled="formHasError"
@click="saveGeoNode(nodeData)"
>{{ saveButtonTitle }}</gl-deprecated-button
>{{ saveButtonTitle }}</gl-button
>
<gl-deprecated-button id="node-cancel-button" class="ml-auto" @click="redirect">{{
<gl-button id="node-cancel-button" class="gl-ml-auto" @click="redirect">{{
__('Cancel')
}}</gl-deprecated-button>
}}</gl-button>
</section>
</form>
</template>
<script>
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale';
import { validateCapacity } from '../validations';
import { VALIDATION_FIELD_KEYS } from '../constants';
export default {
name: 'GeoNodeFormCapacities',
......@@ -23,7 +26,7 @@ export default {
description: __(
'Control the maximum concurrency of repository backfill for this secondary node',
),
key: 'reposMaxCapacity',
key: VALIDATION_FIELD_KEYS.REPOS_MAX_CAPACITY,
conditional: 'secondary',
},
{
......@@ -32,7 +35,7 @@ export default {
description: __(
'Control the maximum concurrency of LFS/attachment backfill for this secondary node',
),
key: 'filesMaxCapacity',
key: VALIDATION_FIELD_KEYS.FILES_MAX_CAPACITY,
conditional: 'secondary',
},
{
......@@ -41,7 +44,7 @@ export default {
description: __(
'Control the maximum concurrency of container repository operations for this Geo node',
),
key: 'containerRepositoriesMaxCapacity',
key: VALIDATION_FIELD_KEYS.CONTAINER_REPOSITORIES_MAX_CAPACITY,
conditional: 'secondary',
},
{
......@@ -50,7 +53,7 @@ export default {
description: __(
'Control the maximum concurrency of verification operations for this Geo node',
),
key: 'verificationMaxCapacity',
key: VALIDATION_FIELD_KEYS.VERIFICATION_MAX_CAPACITY,
},
{
id: 'node-reverification-interval-field',
......@@ -58,13 +61,14 @@ export default {
description: __(
'Control the minimum interval in days that a repository should be reverified for this primary node',
),
key: 'minimumReverificationInterval',
key: VALIDATION_FIELD_KEYS.MINIMUM_REVERIFICATION_INTERVAL,
conditional: 'primary',
},
],
};
},
computed: {
...mapState(['formErrors']),
visibleFormGroups() {
return this.formGroups.filter(group => {
if (group.conditional) {
......@@ -76,6 +80,15 @@ export default {
});
},
},
methods: {
...mapActions(['setError']),
checkCapacity(formGroup) {
this.setError({
key: formGroup.key,
error: validateCapacity({ data: this.nodeData[formGroup.key], label: formGroup.label }),
});
},
},
};
</script>
......@@ -87,12 +100,16 @@ export default {
:label="formGroup.label"
:label-for="formGroup.id"
:description="formGroup.description"
:state="Boolean(formErrors[formGroup.key])"
:invalid-feedback="formErrors[formGroup.key]"
>
<gl-form-input
:id="formGroup.id"
v-model="nodeData[formGroup.key]"
:class="{ 'is-invalid': Boolean(formErrors[formGroup.key]) }"
class="col-sm-3"
type="number"
@input="checkCapacity(formGroup)"
/>
</gl-form-group>
</div>
......
<script>
import { GlFormGroup, GlFormInput, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
import { isSafeURL } from '~/lib/utils/url_utility';
import { mapActions, mapState } from 'vuex';
import { validateName, validateUrl } from '../validations';
import { VALIDATION_FIELD_KEYS } from '../constants';
export default {
name: 'GeoNodeFormCore',
......@@ -16,29 +17,16 @@ export default {
required: true,
},
},
data() {
return {
fieldBlurs: {
name: false,
url: false,
},
errors: {
name: __('Name must be between 1 and 255 characters'),
url: __('URL must be a valid url (ex: https://gitlab.com)'),
},
};
},
computed: {
validName() {
return !(this.fieldBlurs.name && (!this.nodeData.name || this.nodeData.name.length > 255));
},
validUrl() {
return !(this.fieldBlurs.url && !isSafeURL(this.nodeData.url));
},
...mapState(['formErrors']),
},
methods: {
blur(field) {
this.fieldBlurs[field] = true;
...mapActions(['setError']),
checkName() {
this.setError({ key: VALIDATION_FIELD_KEYS.NAME, error: validateName(this.nodeData.name) });
},
checkUrl() {
this.setError({ key: VALIDATION_FIELD_KEYS.URL, error: validateUrl(this.nodeData.url) });
},
},
};
......@@ -50,8 +38,8 @@ export default {
class="col-sm-6"
:label="__('Name')"
label-for="node-name-field"
:state="validName"
:invalid-feedback="errors.name"
:state="Boolean(formErrors.name)"
:invalid-feedback="formErrors.name"
>
<template #description>
<gl-sprintf
......@@ -72,9 +60,10 @@ export default {
<gl-form-input
id="node-name-field"
v-model="nodeData.name"
:class="{ 'is-invalid': Boolean(formErrors.name) }"
data-qa-selector="node_name_field"
type="text"
@blur="blur('name')"
@input="checkName"
/>
</gl-form-group>
<gl-form-group
......@@ -82,15 +71,16 @@ export default {
:label="__('URL')"
label-for="node-url-field"
:description="__('The user-facing URL of the Geo node')"
:state="validUrl"
:invalid-feedback="errors.url"
:state="Boolean(formErrors.url)"
:invalid-feedback="formErrors.url"
>
<gl-form-input
id="node-url-field"
v-model="nodeData.url"
:class="{ 'is-invalid': Boolean(formErrors.url) }"
data-qa-selector="node_url_field"
type="text"
@blur="blur('url')"
@input="checkUrl"
/>
</gl-form-group>
</section>
......
export const SELECTIVE_SYNC_SHARDS = 'selectiveSyncShards';
export const SELECTIVE_SYNC_NAMESPACES = 'selectiveSyncNamespaceIds';
export const VALIDATION_FIELD_KEYS = {
NAME: 'name',
URL: 'url',
REPOS_MAX_CAPACITY: 'reposMaxCapacity',
FILES_MAX_CAPACITY: 'filesMaxCapacity',
CONTAINER_REPOSITORIES_MAX_CAPACITY: 'containerRepositoriesMaxCapacity',
VERIFICATION_MAX_CAPACITY: 'verificationMaxCapacity',
MINIMUM_REVERIFICATION_INTERVAL: 'minimumReverificationInterval',
};
......@@ -64,3 +64,5 @@ export const saveGeoNode = ({ dispatch }, node) => {
dispatch('receiveSaveGeoNodeError', response.data);
});
};
export const setError = ({ commit }, { key, error }) => commit(types.SET_ERROR, { key, error });
// eslint-disable-next-line import/prefer-default-export
export const formHasError = state => Object.values(state.formErrors).some(val => Boolean(val));
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import createState from './state';
......@@ -10,6 +11,7 @@ const createStore = () =>
new Vuex.Store({
actions,
mutations,
getters,
state: createState(),
});
export default createStore;
......@@ -4,3 +4,5 @@ export const RECEIVE_SYNC_NAMESPACES_ERROR = 'RECEIVE_SYNC_NAMESPACES_ERROR';
export const REQUEST_SAVE_GEO_NODE = 'REQUEST_SAVE_GEO_NODE';
export const RECEIVE_SAVE_GEO_NODE_COMPLETE = 'RECEIVE_SAVE_GEO_NODE_COMPLETE';
export const SET_ERROR = 'SET_ERROR';
......@@ -18,4 +18,7 @@ export default {
[types.RECEIVE_SAVE_GEO_NODE_COMPLETE](state) {
state.isLoading = false;
},
[types.SET_ERROR](state, { key, error }) {
state.formErrors[key] = error;
},
};
import { VALIDATION_FIELD_KEYS } from '../constants';
const createState = () => ({
isLoading: false,
synchronizationNamespaces: [],
formErrors: Object.values(VALIDATION_FIELD_KEYS).reduce(
(acc, cur) => ({ ...acc, [cur]: '' }),
{},
),
});
export default createState;
import { sprintf, s__ } from '~/locale';
import { isSafeURL } from '~/lib/utils/url_utility';
export const validateName = data => {
if (!data) {
return s__("Geo|Node name can't be blank");
} else if (data.length > 255) {
return s__('Geo|Node name should be between 1 and 255 characters');
}
return '';
};
export const validateUrl = data => {
if (!data) {
return s__("Geo|URL can't be blank");
} else if (!isSafeURL(data)) {
return s__('Geo|URL must be a valid url (ex: https://gitlab.com)');
}
return '';
};
export const validateCapacity = ({ data, label }) => {
if (!data && data !== 0) {
return sprintf(s__("Geo|%{label} can't be blank"), { label });
} else if (data < 1 || data > 999) {
return sprintf(s__('Geo|%{label} should be between 1-999'), { label });
}
return '';
};
---
title: Geo Form Validations
merge_request: 32263
author:
type: changed
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import GeoNodeFormCapacities from 'ee/geo_node_form/components/geo_node_form_capacities.vue';
import { VALIDATION_FIELD_KEYS } from 'ee/geo_node_form/constants';
import { MOCK_NODE } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('GeoNodeFormCapacities', () => {
let wrapper;
let store;
const propsData = {
const defaultProps = {
nodeData: MOCK_NODE,
};
const createComponent = () => {
wrapper = shallowMount(GeoNodeFormCapacities, {
propsData,
const createComponent = (props = {}) => {
store = new Vuex.Store({
state: {
formErrors: Object.values(VALIDATION_FIELD_KEYS).reduce(
(acc, cur) => ({ ...acc, [cur]: '' }),
{},
),
},
actions: {
setError({ state }, { key, error }) {
state.formErrors[key] = error;
},
},
});
wrapper = mount(GeoNodeFormCapacities, {
localVue,
store,
propsData: {
...defaultProps,
...props,
},
});
};
......@@ -28,6 +53,7 @@ describe('GeoNodeFormCapacities', () => {
wrapper.find('#node-verification-capacity-field');
const findGeoNodeFormReverificationIntervalField = () =>
wrapper.find('#node-reverification-interval-field');
const findErrorMessage = () => wrapper.find('.invalid-feedback');
describe('template', () => {
describe.each`
......@@ -45,8 +71,9 @@ describe('GeoNodeFormCapacities', () => {
showReverificationInterval,
}) => {
beforeEach(() => {
propsData.nodeData.primary = primaryNode;
createComponent();
createComponent({
nodeData: { ...defaultProps.nodeData, primary: primaryNode },
});
});
it(`it ${showRepoCapacity ? 'shows' : 'hides'} the Repository Capacity Field`, () => {
......@@ -82,14 +109,128 @@ describe('GeoNodeFormCapacities', () => {
});
},
);
describe.each`
data | showError | errorMessage
${null} | ${true} | ${"can't be blank"}
${''} | ${true} | ${"can't be blank"}
${-1} | ${true} | ${'should be between 1-999'}
${0} | ${true} | ${'should be between 1-999'}
${1} | ${false} | ${null}
${999} | ${false} | ${null}
${1000} | ${true} | ${'should be between 1-999'}
`(`errors`, ({ data, showError, errorMessage }) => {
describe('on primary node', () => {
beforeEach(() => {
createComponent({
nodeData: { ...defaultProps.nodeData, primary: true },
});
});
describe('Verification Capacity Field', () => {
beforeEach(() => {
findGeoNodeFormVerificationCapacityField().setValue(data);
});
it(`${showError ? 'shows' : 'hides'} error when data is ${data}`, () => {
expect(findGeoNodeFormVerificationCapacityField().classes('is-invalid')).toBe(
showError,
);
if (showError) {
expect(findErrorMessage().text()).toBe(`Verification capacity ${errorMessage}`);
}
});
});
describe('Reverification Interval Field', () => {
beforeEach(() => {
findGeoNodeFormReverificationIntervalField().setValue(data);
});
it(`${showError ? 'shows' : 'hides'} error when data is ${data}`, () => {
expect(findGeoNodeFormReverificationIntervalField().classes('is-invalid')).toBe(
showError,
);
if (showError) {
expect(findErrorMessage().text()).toBe(`Re-verification interval ${errorMessage}`);
}
});
});
});
describe('on secondary node', () => {
beforeEach(() => {
createComponent();
});
describe('Repository Capacity Field', () => {
beforeEach(() => {
findGeoNodeFormRepositoryCapacityField().setValue(data);
});
it(`${showError ? 'shows' : 'hides'} error when data is ${data}`, () => {
expect(findGeoNodeFormRepositoryCapacityField().classes('is-invalid')).toBe(showError);
if (showError) {
expect(findErrorMessage().text()).toBe(`Repository sync capacity ${errorMessage}`);
}
});
});
describe('File Capacity Field', () => {
beforeEach(() => {
findGeoNodeFormFileCapacityField().setValue(data);
});
it(`${showError ? 'shows' : 'hides'} error when data is ${data}`, () => {
expect(findGeoNodeFormFileCapacityField().classes('is-invalid')).toBe(showError);
if (showError) {
expect(findErrorMessage().text()).toBe(`File sync capacity ${errorMessage}`);
}
});
});
describe('Container Repository Capacity Field', () => {
beforeEach(() => {
findGeoNodeFormContainerRepositoryCapacityField().setValue(data);
});
it(`${showError ? 'shows' : 'hides'} error when data is ${data}`, () => {
expect(findGeoNodeFormContainerRepositoryCapacityField().classes('is-invalid')).toBe(
showError,
);
if (showError) {
expect(findErrorMessage().text()).toBe(
`Container repositories sync capacity ${errorMessage}`,
);
}
});
});
describe('Verification Capacity Field', () => {
beforeEach(() => {
findGeoNodeFormVerificationCapacityField().setValue(data);
});
it(`${showError ? 'shows' : 'hides'} error when data is ${data}`, () => {
expect(findGeoNodeFormVerificationCapacityField().classes('is-invalid')).toBe(
showError,
);
if (showError) {
expect(findErrorMessage().text()).toBe(`Verification capacity ${errorMessage}`);
}
});
});
});
});
});
describe('computed', () => {
describe('visibleFormGroups', () => {
describe('when nodeData.primary is true', () => {
beforeEach(() => {
propsData.nodeData.primary = true;
createComponent();
createComponent({
nodeData: { ...defaultProps.nodeData, primary: true },
});
});
it('contains conditional form groups for primary', () => {
......@@ -103,7 +244,6 @@ describe('GeoNodeFormCapacities', () => {
describe('when nodeData.primary is false', () => {
beforeEach(() => {
propsData.nodeData.primary = false;
createComponent();
});
......
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import GeoNodeFormCore from 'ee/geo_node_form/components/geo_node_form_core.vue';
import { VALIDATION_FIELD_KEYS } from 'ee/geo_node_form/constants';
import { MOCK_NODE, STRING_OVER_255 } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('GeoNodeFormCore', () => {
let wrapper;
let store;
const defaultProps = {
nodeData: MOCK_NODE,
};
const createComponent = (props = {}) => {
wrapper = shallowMount(GeoNodeFormCore, {
store = new Vuex.Store({
state: {
formErrors: Object.values(VALIDATION_FIELD_KEYS).reduce(
(acc, cur) => ({ ...acc, [cur]: '' }),
{},
),
},
actions: {
setError({ state }, { key, error }) {
state.formErrors[key] = error;
},
},
});
wrapper = mount(GeoNodeFormCore, {
localVue,
store,
propsData: {
...defaultProps,
...props,
......@@ -24,6 +46,7 @@ describe('GeoNodeFormCore', () => {
const findGeoNodeFormNameField = () => wrapper.find('#node-name-field');
const findGeoNodeFormUrlField = () => wrapper.find('#node-url-field');
const findErrorMessage = () => wrapper.find('.invalid-feedback');
describe('template', () => {
beforeEach(() => {
......@@ -37,68 +60,46 @@ describe('GeoNodeFormCore', () => {
it('renders Geo Node Form Url Field', () => {
expect(findGeoNodeFormUrlField().exists()).toBe(true);
});
});
describe('computed', () => {
describe.each`
data | dataDesc | blur | value
${''} | ${'empty'} | ${false} | ${true}
${''} | ${'empty'} | ${true} | ${false}
${STRING_OVER_255} | ${'over 255 chars'} | ${false} | ${true}
${STRING_OVER_255} | ${'over 255 chars'} | ${true} | ${false}
${'Test'} | ${'valid'} | ${false} | ${true}
${'Test'} | ${'valid'} | ${true} | ${true}
`(`validName`, ({ data, dataDesc, blur, value }) => {
beforeEach(() => {
createComponent({
nodeData: { ...defaultProps.nodeData, name: data },
describe('errors', () => {
describe.each`
data | showError | errorMessage
${null} | ${true} | ${"Node name can't be blank"}
${''} | ${true} | ${"Node name can't be blank"}
${STRING_OVER_255} | ${true} | ${'Node name should be between 1 and 255 characters'}
${'Test'} | ${false} | ${null}
`(`Name Field`, ({ data, showError, errorMessage }) => {
beforeEach(() => {
createComponent();
findGeoNodeFormNameField().setValue(data);
});
});
describe(`when data is: ${dataDesc}`, () => {
it(`returns ${value} when blur is ${blur}`, () => {
wrapper.vm.fieldBlurs.name = blur;
expect(wrapper.vm.validName).toBe(value);
it(`${showError ? 'shows' : 'hides'} error when data is ${data}`, () => {
expect(findGeoNodeFormNameField().classes('is-invalid')).toBe(showError);
if (showError) {
expect(findErrorMessage().text()).toBe(errorMessage);
}
});
});
});
describe.each`
data | dataDesc | blur | value
${''} | ${'empty'} | ${false} | ${true}
${''} | ${'empty'} | ${true} | ${false}
${'abcd'} | ${'invalid url'} | ${false} | ${true}
${'abcd'} | ${'invalid url'} | ${true} | ${false}
${'https://gitlab.com'} | ${'valid url'} | ${false} | ${true}
${'https://gitlab.com'} | ${'valid url'} | ${true} | ${true}
`(`validUrl`, ({ data, dataDesc, blur, value }) => {
beforeEach(() => {
createComponent({
nodeData: { ...defaultProps.nodeData, url: data },
});
});
describe(`when data is: ${dataDesc}`, () => {
it(`returns ${value} when blur is ${blur}`, () => {
wrapper.vm.fieldBlurs.url = blur;
expect(wrapper.vm.validUrl).toBe(value);
});
});
});
});
describe('methods', () => {
describe('blur', () => {
data | showError | errorMessage
${null} | ${true} | ${"URL can't be blank"}
${''} | ${true} | ${"URL can't be blank"}
${'abcd'} | ${true} | ${'URL must be a valid url (ex: https://gitlab.com)'}
${'https://gitlab.com'} | ${false} | ${null}
`(`Name Field`, ({ data, showError, errorMessage }) => {
beforeEach(() => {
createComponent();
findGeoNodeFormUrlField().setValue(data);
});
it('sets fieldBlur[field] to true', () => {
expect(wrapper.vm.fieldBlurs.name).toBeFalsy();
wrapper.vm.blur('name');
expect(wrapper.vm.fieldBlurs.name).toBeTruthy();
it(`${showError ? 'shows' : 'hides'} error when data is ${data}`, () => {
expect(findGeoNodeFormUrlField().classes('is-invalid')).toBe(showError);
if (showError) {
expect(findErrorMessage().text()).toBe(errorMessage);
}
});
});
});
......
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { visitUrl } from '~/lib/utils/url_utility';
import GeoNodeForm from 'ee/geo_node_form/components/geo_node_form.vue';
import GeoNodeFormCore from 'ee/geo_node_form/components/geo_node_form_core.vue';
import GeoNodeFormCapacities from 'ee/geo_node_form/components/geo_node_form_capacities.vue';
import store from 'ee/geo_node_form/store';
import { MOCK_NODE, MOCK_SELECTIVE_SYNC_TYPES, MOCK_SYNC_SHARDS } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn().mockName('visitUrlMock'),
}));
......@@ -20,6 +25,7 @@ describe('GeoNodeForm', () => {
const createComponent = () => {
wrapper = shallowMount(GeoNodeForm, {
store,
propsData,
});
};
......@@ -70,6 +76,24 @@ describe('GeoNodeForm', () => {
});
},
);
describe('Save Button', () => {
describe('with errors on form', () => {
beforeEach(() => {
wrapper.vm.$store.state.formErrors.name = 'Test Error';
});
it('disables button', () => {
expect(findGeoNodeSaveButton().attributes('disabled')).toBeTruthy();
});
});
describe('with mo errors on form', () => {
it('does not disable button', () => {
expect(findGeoNodeSaveButton().attributes('disabled')).toBeFalsy();
});
});
});
});
describe('methods', () => {
......
......@@ -44,6 +44,7 @@ describe('GeoNodeForm Store Actions', () => {
${actions.requestSaveGeoNode} | ${null} | ${types.REQUEST_SAVE_GEO_NODE} | ${{ type: types.REQUEST_SAVE_GEO_NODE }} | ${noCallback}
${actions.receiveSaveGeoNodeSuccess} | ${null} | ${types.RECEIVE_SAVE_GEO_NODE_COMPLETE} | ${{ type: types.RECEIVE_SAVE_GEO_NODE_COMPLETE }} | ${visitUrlCallback}
${actions.receiveSaveGeoNodeError} | ${{ message: MOCK_ERROR_MESSAGE }} | ${types.RECEIVE_SAVE_GEO_NODE_COMPLETE} | ${{ type: types.RECEIVE_SAVE_GEO_NODE_COMPLETE }} | ${flashCallback}
${actions.setError} | ${{ key: 'name', error: 'error' }} | ${types.SET_ERROR} | ${{ type: types.SET_ERROR, payload: { key: 'name', error: 'error' } }} | ${noCallback}
`(`non-axios calls`, ({ action, data, mutationName, mutationCall, callback }) => {
describe(action.name, () => {
it(`should commit mutation ${mutationName}`, () => {
......
import * as getters from 'ee/geo_node_form/store/getters';
import createState from 'ee/geo_node_form/store/state';
describe('GeoNodeForm Store Getters', () => {
let state;
beforeEach(() => {
state = createState();
});
describe('formHasError', () => {
it('with error returns true', () => {
state.formErrors.name = 'Error';
expect(getters.formHasError(state)).toBeTruthy();
});
it('without error returns false', () => {
expect(getters.formHasError(state)).toBeFalsy();
});
});
});
......@@ -43,4 +43,11 @@ describe('GeoNodeForm Store Mutations', () => {
expect(state.synchronizationNamespaces).toEqual([]);
});
});
describe('SET_ERROR', () => {
it('sets error for field', () => {
mutations[types.SET_ERROR](state, { key: 'name', error: 'error' });
expect(state.formErrors.name).toBe('error');
});
});
});
import { validateName, validateUrl, validateCapacity } from 'ee/geo_node_form/validations';
import { STRING_OVER_255 } from './mock_data';
describe('GeoNodeForm Validations', () => {
describe.each`
data | errorMessage
${null} | ${"Node name can't be blank"}
${''} | ${"Node name can't be blank"}
${STRING_OVER_255} | ${'Node name should be between 1 and 255 characters'}
${'Test'} | ${''}
`(`validateName`, ({ data, errorMessage }) => {
let validateNameRes = '';
beforeEach(() => {
validateNameRes = validateName(data);
});
it(`return ${errorMessage} when data is ${data}`, () => {
expect(validateNameRes).toBe(errorMessage);
});
});
describe.each`
data | errorMessage
${null} | ${"URL can't be blank"}
${''} | ${"URL can't be blank"}
${'abcd'} | ${'URL must be a valid url (ex: https://gitlab.com)'}
${'https://gitlab.com'} | ${''}
`(`validateUrl`, ({ data, errorMessage }) => {
let validateUrlRes = '';
beforeEach(() => {
validateUrlRes = validateUrl(data);
});
it(`return ${errorMessage} when data is ${data}`, () => {
expect(validateUrlRes).toBe(errorMessage);
});
});
describe.each`
data | errorMessage
${null} | ${"Mock field can't be blank"}
${''} | ${"Mock field can't be blank"}
${-1} | ${'Mock field should be between 1-999'}
${0} | ${'Mock field should be between 1-999'}
${1} | ${''}
${999} | ${''}
${1000} | ${'Mock field should be between 1-999'}
`(`validateCapacity`, ({ data, errorMessage }) => {
let validateCapacityRes = '';
beforeEach(() => {
validateCapacityRes = validateCapacity({ data, label: 'Mock field' });
});
it(`return ${errorMessage} when data is ${data}`, () => {
expect(validateCapacityRes).toBe(errorMessage);
});
});
});
......@@ -10031,6 +10031,12 @@ msgstr ""
msgid "GeoNodes|secondary nodes"
msgstr ""
msgid "Geo|%{label} can't be blank"
msgstr ""
msgid "Geo|%{label} should be between 1-999"
msgstr ""
msgid "Geo|%{name} is scheduled for forced re-download"
msgstr ""
......@@ -10094,6 +10100,12 @@ msgstr ""
msgid "Geo|Next sync scheduled at"
msgstr ""
msgid "Geo|Node name can't be blank"
msgstr ""
msgid "Geo|Node name should be between 1 and 255 characters"
msgstr ""
msgid "Geo|Not synced yet"
msgstr ""
......@@ -10178,6 +10190,12 @@ msgstr ""
msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
msgstr ""
msgid "Geo|URL can't be blank"
msgstr ""
msgid "Geo|URL must be a valid url (ex: https://gitlab.com)"
msgstr ""
msgid "Geo|Unknown state"
msgstr ""
......@@ -14004,9 +14022,6 @@ msgstr ""
msgid "Name has already been taken"
msgstr ""
msgid "Name must be between 1 and 255 characters"
msgstr ""
msgid "Name new label"
msgstr ""
......@@ -23022,9 +23037,6 @@ msgstr ""
msgid "URL is required"
msgstr ""
msgid "URL must be a valid url (ex: https://gitlab.com)"
msgstr ""
msgid "URL must start with %{codeStart}http://%{codeEnd}, %{codeStart}https://%{codeEnd}, or %{codeStart}ftp://%{codeEnd}"
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