Commit 6c19a853 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Natalia Tepluhina

Move utils and constants to shared

- constants moved
- utils moved and improved
parent eddb1dac
...@@ -3,7 +3,7 @@ import { mapActions, mapState } from 'vuex'; ...@@ -3,7 +3,7 @@ import { mapActions, mapState } from 'vuex';
import { GlAlert } from '@gitlab/ui'; import { GlAlert } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import { FETCH_SETTINGS_ERROR_MESSAGE } from '../constants'; import { FETCH_SETTINGS_ERROR_MESSAGE } from '../../shared/constants';
import SettingsForm from './settings_form.vue'; import SettingsForm from './settings_form.vue';
......
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import {
GlFormGroup,
GlToggle,
GlFormSelect,
GlFormTextarea,
GlButton,
GlCard,
GlLoadingIcon,
} from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { import {
NAME_REGEX_LENGTH,
UPDATE_SETTINGS_ERROR_MESSAGE, UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE, UPDATE_SETTINGS_SUCCESS_MESSAGE,
} from '../constants'; } from '../../shared/constants';
import { mapComputed } from '~/vuex_shared/bindings'; import { mapComputed } from '~/vuex_shared/bindings';
import ExpirationPolicyForm from '../../shared/components/expiration_policy_form.vue';
export default { export default {
components: { components: {
GlFormGroup, ExpirationPolicyForm,
GlToggle,
GlFormSelect,
GlFormTextarea,
GlButton,
GlCard,
GlLoadingIcon,
}, },
mixins: [Tracking.mixin()], mixins: [Tracking.mixin()],
labelsConfig: { labelsConfig: {
...@@ -43,59 +27,7 @@ export default { ...@@ -43,59 +27,7 @@ export default {
computed: { computed: {
...mapState(['formOptions', 'isLoading']), ...mapState(['formOptions', 'isLoading']),
...mapGetters({ isEdited: 'getIsEdited' }), ...mapGetters({ isEdited: 'getIsEdited' }),
...mapComputed( ...mapComputed([{ key: 'settings', getter: 'getSettings' }], 'updateSettings'),
[
'enabled',
{ key: 'cadence', getter: 'getCadence' },
{ key: 'older_than', getter: 'getOlderThan' },
{ key: 'keep_n', getter: 'getKeepN' },
'name_regex',
],
'updateSettings',
'settings',
),
policyEnabledText() {
return this.enabled ? __('enabled') : __('disabled');
},
toggleDescriptionText() {
return sprintf(
s__('ContainerRegistry|Docker tag expiration policy is %{toggleStatus}'),
{
toggleStatus: `<strong>${this.policyEnabledText}</strong>`,
},
false,
);
},
regexHelpText() {
return sprintf(
s__(
'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
),
{
codeStart: '<code>',
codeEnd: '</code>',
},
false,
);
},
nameRegexPlaceholder() {
return '.*';
},
nameRegexState() {
return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null;
},
formIsInvalid() {
return this.nameRegexState === false;
},
isFormElementDisabled() {
return !this.enabled || this.isLoading;
},
isSubmitButtonDisabled() {
return this.formIsInvalid || this.isLoading;
},
isCancelButtonDisabled() {
return !this.isEdited || this.isLoading;
},
}, },
methods: { methods: {
...mapActions(['resetSettings', 'saveSettings']), ...mapActions(['resetSettings', 'saveSettings']),
...@@ -114,127 +46,12 @@ export default { ...@@ -114,127 +46,12 @@ export default {
</script> </script>
<template> <template>
<form ref="form-element" @submit.prevent="submit" @reset.prevent="reset"> <expiration-policy-form
<gl-card> v-model="settings"
<template #header> :form-options="formOptions"
{{ s__('ContainerRegistry|Tag expiration policy') }} :is-loading="isLoading"
</template> :disable-cancel-button="!isEdited"
<template> @submit="submit"
<gl-form-group @reset="reset"
id="expiration-policy-toggle-group" />
:label-cols="$options.labelsConfig.cols"
:label-align="$options.labelsConfig.align"
label-for="expiration-policy-toggle"
:label="s__('ContainerRegistry|Expiration policy:')"
>
<div class="d-flex align-items-start">
<gl-toggle id="expiration-policy-toggle" v-model="enabled" :disabled="isLoading" />
<span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span>
</div>
</gl-form-group>
<gl-form-group
id="expiration-policy-interval-group"
:label-cols="$options.labelsConfig.cols"
:label-align="$options.labelsConfig.align"
label-for="expiration-policy-interval"
:label="s__('ContainerRegistry|Expiration interval:')"
>
<gl-form-select
id="expiration-policy-interval"
v-model="older_than"
:disabled="isFormElementDisabled"
>
<option v-for="option in formOptions.olderThan" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</gl-form-select>
</gl-form-group>
<gl-form-group
id="expiration-policy-schedule-group"
:label-cols="$options.labelsConfig.cols"
:label-align="$options.labelsConfig.align"
label-for="expiration-policy-schedule"
:label="s__('ContainerRegistry|Expiration schedule:')"
>
<gl-form-select
id="expiration-policy-schedule"
v-model="cadence"
:disabled="isFormElementDisabled"
>
<option v-for="option in formOptions.cadence" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</gl-form-select>
</gl-form-group>
<gl-form-group
id="expiration-policy-latest-group"
:label-cols="$options.labelsConfig.cols"
:label-align="$options.labelsConfig.align"
label-for="expiration-policy-latest"
:label="s__('ContainerRegistry|Number of tags to retain:')"
>
<gl-form-select
id="expiration-policy-latest"
v-model="keep_n"
:disabled="isFormElementDisabled"
>
<option v-for="option in formOptions.keepN" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</gl-form-select>
</gl-form-group>
<gl-form-group
id="expiration-policy-name-matching-group"
:label-cols="$options.labelsConfig.cols"
:label-align="$options.labelsConfig.align"
label-for="expiration-policy-name-matching"
:label="
s__('ContainerRegistry|Docker tags with names matching this regex pattern will expire:')
"
:state="nameRegexState"
:invalid-feedback="
s__('ContainerRegistry|The value of this input should be less than 255 characters')
"
>
<gl-form-textarea
id="expiration-policy-name-matching"
v-model="name_regex"
:placeholder="nameRegexPlaceholder"
:state="nameRegexState"
:disabled="isFormElementDisabled"
trim
/>
<template #description>
<span ref="regex-description" v-html="regexHelpText"></span>
</template>
</gl-form-group>
</template>
<template #footer>
<div class="d-flex justify-content-end">
<gl-button
ref="cancel-button"
type="reset"
:disabled="isCancelButtonDisabled"
class="mr-2 d-block"
>
{{ __('Cancel') }}
</gl-button>
<gl-button
ref="save-button"
type="submit"
:disabled="isSubmitButtonDisabled"
variant="success"
class="d-flex justify-content-center align-items-center js-no-auto-disable"
>
{{ __('Save expiration policy') }}
<gl-loading-icon v-if="isLoading" class="ml-2" />
</gl-button>
</div>
</template>
</gl-card>
</form>
</template> </template>
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { findDefaultOption } from '../utils'; import { findDefaultOption } from '../../shared/utils';
export const getCadence = state => export const getCadence = state =>
state.settings.cadence || findDefaultOption(state.formOptions.cadence); state.settings.cadence || findDefaultOption(state.formOptions.cadence);
export const getKeepN = state => export const getKeepN = state =>
state.settings.keep_n || findDefaultOption(state.formOptions.keepN); state.settings.keep_n || findDefaultOption(state.formOptions.keepN);
export const getOlderThan = state => export const getOlderThan = state =>
state.settings.older_than || findDefaultOption(state.formOptions.olderThan); state.settings.older_than || findDefaultOption(state.formOptions.olderThan);
export const getSettings = (state, getters) => ({
enabled: state.settings.enabled,
cadence: getters.getCadence,
older_than: getters.getOlderThan,
keep_n: getters.getKeepN,
name_regex: state.settings.name_regex,
});
export const getIsEdited = state => !isEqual(state.original, state.settings); export const getIsEdited = state => !isEqual(state.original, state.settings);
...@@ -9,8 +9,8 @@ export default { ...@@ -9,8 +9,8 @@ export default {
olderThan: JSON.parse(initialState.olderThanOptions), olderThan: JSON.parse(initialState.olderThanOptions),
}; };
}, },
[types.UPDATE_SETTINGS](state, settings) { [types.UPDATE_SETTINGS](state, data) {
state.settings = { ...state.settings, ...settings }; state.settings = { ...state.settings, ...data.settings };
}, },
[types.SET_SETTINGS](state, settings) { [types.SET_SETTINGS](state, settings) {
state.settings = settings; state.settings = settings;
......
<script>
import { uniqueId } from 'lodash';
import {
GlFormGroup,
GlToggle,
GlFormSelect,
GlFormTextarea,
GlButton,
GlCard,
GlLoadingIcon,
} from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale';
import { NAME_REGEX_LENGTH } from '../constants';
import { mapComputedToEvent } from '../utils';
export default {
components: {
GlFormGroup,
GlToggle,
GlFormSelect,
GlFormTextarea,
GlButton,
GlCard,
GlLoadingIcon,
},
props: {
formOptions: {
type: Object,
required: false,
default: () => ({}),
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
value: {
type: Object,
required: false,
default: () => ({}),
},
labelCols: {
type: [Number, String],
required: false,
default: 3,
},
labelAlign: {
type: String,
required: false,
default: 'right',
},
disableCancelButton: {
type: Boolean,
required: false,
default: false,
},
},
nameRegexPlaceholder: '.*',
data() {
return {
uniqueId: uniqueId(),
};
},
computed: {
...mapComputedToEvent(['enabled', 'cadence', 'older_than', 'keep_n', 'name_regex'], 'value'),
policyEnabledText() {
return this.enabled ? __('enabled') : __('disabled');
},
toggleDescriptionText() {
return sprintf(
s__('ContainerRegistry|Docker tag expiration policy is %{toggleStatus}'),
{
toggleStatus: `<strong>${this.policyEnabledText}</strong>`,
},
false,
);
},
regexHelpText() {
return sprintf(
s__(
'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
),
{
codeStart: '<code>',
codeEnd: '</code>',
},
false,
);
},
nameRegexState() {
return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null;
},
formIsInvalid() {
return this.nameRegexState === false;
},
isFormElementDisabled() {
return !this.enabled || this.isLoading;
},
isSubmitButtonDisabled() {
return this.formIsInvalid || this.isLoading;
},
isCancelButtonDisabled() {
return this.disableCancelButton || this.isLoading;
},
},
methods: {
idGenerator(id) {
return `${id}_${this.uniqueId}`;
},
},
};
</script>
<template>
<form
ref="form-element"
class="lh-2"
@submit.prevent="$emit('submit')"
@reset.prevent="$emit('reset')"
>
<gl-card>
<template #header>
{{ s__('ContainerRegistry|Tag expiration policy') }}
</template>
<template>
<gl-form-group
:id="idGenerator('expiration-policy-toggle-group')"
:label-cols="labelCols"
:label-align="labelAlign"
:label-for="idGenerator('expiration-policy-toggle')"
:label="s__('ContainerRegistry|Expiration policy:')"
>
<div class="d-flex align-items-start">
<gl-toggle
:id="idGenerator('expiration-policy-toggle')"
v-model="enabled"
:disabled="isLoading"
/>
<span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span>
</div>
</gl-form-group>
<gl-form-group
:id="idGenerator('expiration-policy-interval-group')"
:label-cols="labelCols"
:label-align="labelAlign"
:label-for="idGenerator('expiration-policy-interval')"
:label="s__('ContainerRegistry|Expiration interval:')"
>
<gl-form-select
:id="idGenerator('expiration-policy-interval')"
v-model="older_than"
:disabled="isFormElementDisabled"
>
<option v-for="option in formOptions.olderThan" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</gl-form-select>
</gl-form-group>
<gl-form-group
:id="idGenerator('expiration-policy-schedule-group')"
:label-cols="labelCols"
:label-align="labelAlign"
:label-for="idGenerator('expiration-policy-schedule')"
:label="s__('ContainerRegistry|Expiration schedule:')"
>
<gl-form-select
:id="idGenerator('expiration-policy-schedule')"
v-model="cadence"
:disabled="isFormElementDisabled"
>
<option v-for="option in formOptions.cadence" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</gl-form-select>
</gl-form-group>
<gl-form-group
:id="idGenerator('expiration-policy-latest-group')"
:label-cols="labelCols"
:label-align="labelAlign"
:label-for="idGenerator('expiration-policy-latest')"
:label="s__('ContainerRegistry|Number of tags to retain:')"
>
<gl-form-select
:id="idGenerator('expiration-policy-latest')"
v-model="keep_n"
:disabled="isFormElementDisabled"
>
<option v-for="option in formOptions.keepN" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</gl-form-select>
</gl-form-group>
<gl-form-group
:id="idGenerator('expiration-policy-name-matching-group')"
:label-cols="labelCols"
:label-align="labelAlign"
:label-for="idGenerator('expiration-policy-name-matching')"
:label="
s__('ContainerRegistry|Docker tags with names matching this regex pattern will expire:')
"
:state="nameRegexState"
:invalid-feedback="
s__('ContainerRegistry|The value of this input should be less than 255 characters')
"
>
<gl-form-textarea
:id="idGenerator('expiration-policy-name-matching')"
v-model="name_regex"
:placeholder="$options.nameRegexPlaceholder"
:state="nameRegexState"
:disabled="isFormElementDisabled"
trim
/>
<template #description>
<span ref="regex-description" v-html="regexHelpText"></span>
</template>
</gl-form-group>
</template>
<template #footer>
<div class="d-flex justify-content-end">
<gl-button
ref="cancel-button"
type="reset"
class="mr-2 d-block"
:disabled="isCancelButtonDisabled"
>
{{ __('Cancel') }}
</gl-button>
<gl-button
ref="save-button"
type="submit"
:disabled="isSubmitButtonDisabled"
variant="success"
class="d-flex justify-content-center align-items-center js-no-auto-disable"
>
{{ __('Save expiration policy') }}
<gl-loading-icon v-if="isLoading" class="ml-2" />
</gl-button>
</div>
</template>
</gl-card>
</form>
</template>
...@@ -3,4 +3,17 @@ export const findDefaultOption = options => { ...@@ -3,4 +3,17 @@ export const findDefaultOption = options => {
return item ? item.key : null; return item ? item.key : null;
}; };
export default () => {}; export const mapComputedToEvent = (list, root) => {
const result = {};
list.forEach(e => {
result[e] = {
get() {
return this[root][e];
},
set(value) {
this.$emit('input', { ...this[root], [e]: value });
},
};
});
return result;
};
...@@ -26,11 +26,11 @@ describe 'Project > Settings > CI/CD > Container registry tag expiration policy' ...@@ -26,11 +26,11 @@ describe 'Project > Settings > CI/CD > Container registry tag expiration policy'
it 'saves expiration policy submit the form' do it 'saves expiration policy submit the form' do
within '#js-registry-policies' do within '#js-registry-policies' do
within '.card-body' do within '.card-body' do
find('#expiration-policy-toggle button:not(.is-disabled)').click find('.gl-toggle-wrapper button:not(.is-disabled)').click
select('7 days until tags are automatically removed', from: 'expiration-policy-interval') select('7 days until tags are automatically removed', from: 'Expiration interval:')
select('Every day', from: 'expiration-policy-schedule') select('Every day', from: 'Expiration schedule:')
select('50 tags per image name', from: 'expiration-policy-latest') select('50 tags per image name', from: 'Number of tags to retain:')
fill_in('expiration-policy-name-matching', with: '*-production') fill_in('Docker tags with names matching this regex pattern will expire:', with: '*-production')
end end
submit_button = find('.card-footer .btn.btn-success') submit_button = find('.card-footer .btn.btn-success')
expect(submit_button).not_to be_disabled expect(submit_button).not_to be_disabled
......
...@@ -4,7 +4,7 @@ import component from '~/registry/settings/components/registry_settings_app.vue' ...@@ -4,7 +4,7 @@ import component from '~/registry/settings/components/registry_settings_app.vue'
import SettingsForm from '~/registry/settings/components/settings_form.vue'; import SettingsForm from '~/registry/settings/components/settings_form.vue';
import { createStore } from '~/registry/settings/store/'; import { createStore } from '~/registry/settings/store/';
import { SET_IS_DISABLED } from '~/registry/settings/store/mutation_types'; import { SET_IS_DISABLED } from '~/registry/settings/store/mutation_types';
import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/settings/constants'; import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/shared/constants';
describe('Registry Settings App', () => { describe('Registry Settings App', () => {
let wrapper; let wrapper;
......
import { mount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import stubChildren from 'helpers/stub_children';
import component from '~/registry/settings/components/settings_form.vue'; import component from '~/registry/settings/components/settings_form.vue';
import expirationPolicyForm from '~/registry/shared/components/expiration_policy_form.vue';
import { createStore } from '~/registry/settings/store/'; import { createStore } from '~/registry/settings/store/';
import { import {
NAME_REGEX_LENGTH,
UPDATE_SETTINGS_ERROR_MESSAGE, UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE, UPDATE_SETTINGS_SUCCESS_MESSAGE,
} from '~/registry/settings/constants'; } from '~/registry/shared/constants';
import { stringifiedFormOptions } from '../mock_data'; import { stringifiedFormOptions } from '../../shared/mock_data';
describe('Settings Form', () => { describe('Settings Form', () => {
let wrapper; let wrapper;
let store; let store;
let dispatchSpy; let dispatchSpy;
const FORM_ELEMENTS_ID_PREFIX = '#expiration-policy';
const trackingPayload = { const trackingPayload = {
label: 'docker_container_retention_and_expiration_policies', label: 'docker_container_retention_and_expiration_policies',
}; };
const GlLoadingIcon = { name: 'gl-loading-icon-stub', template: '<svg></svg>' }; const findForm = () => wrapper.find(expirationPolicyForm);
const findFormGroup = name => wrapper.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}-group`); const mountComponent = () => {
const findFormElements = (name, parent = wrapper) => wrapper = shallowMount(component, {
parent.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}`);
const findCancelButton = () => wrapper.find({ ref: 'cancel-button' });
const findSaveButton = () => wrapper.find({ ref: 'save-button' });
const findForm = () => wrapper.find({ ref: 'form-element' });
const findLoadingIcon = (parent = wrapper) => parent.find(GlLoadingIcon);
const mountComponent = (options = {}) => {
wrapper = mount(component, {
stubs: {
...stubChildren(component),
GlCard: false,
GlLoadingIcon,
},
mocks: { mocks: {
$toast: { $toast: {
show: jest.fn(), show: jest.fn(),
}, },
}, },
store, store,
...options,
}); });
}; };
...@@ -59,170 +43,50 @@ describe('Settings Form', () => { ...@@ -59,170 +43,50 @@ describe('Settings Form', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('renders', () => { describe('form', () => {
expect(wrapper.element).toMatchSnapshot();
});
describe.each`
elementName | modelName | value | disabledByToggle
${'toggle'} | ${'enabled'} | ${true} | ${'not disabled'}
${'interval'} | ${'older_than'} | ${'foo'} | ${'disabled'}
${'schedule'} | ${'cadence'} | ${'foo'} | ${'disabled'}
${'latest'} | ${'keep_n'} | ${'foo'} | ${'disabled'}
${'name-matching'} | ${'name_regex'} | ${'foo'} | ${'disabled'}
`(
`${FORM_ELEMENTS_ID_PREFIX}-$elementName form element`,
({ elementName, modelName, value, disabledByToggle }) => {
let formGroup;
beforeEach(() => {
formGroup = findFormGroup(elementName);
});
it(`${elementName} form group exist in the dom`, () => {
expect(formGroup.exists()).toBe(true);
});
it(`${elementName} form group has a label-for property`, () => {
expect(formGroup.attributes('label-for')).toBe(`expiration-policy-${elementName}`);
});
it(`${elementName} form group has a label-cols property`, () => {
expect(formGroup.attributes('label-cols')).toBe(`${wrapper.vm.$options.labelsConfig.cols}`);
});
it(`${elementName} form group has a label-align property`, () => {
expect(formGroup.attributes('label-align')).toBe(
`${wrapper.vm.$options.labelsConfig.align}`,
);
});
it(`${elementName} form group contains an input element`, () => {
expect(findFormElements(elementName, formGroup).exists()).toBe(true);
});
it(`${elementName} form element change updated ${modelName} with ${value}`, () => {
const element = findFormElements(elementName, formGroup);
const modelUpdateEvent = element.vm.$options.model
? element.vm.$options.model.event
: 'input';
element.vm.$emit(modelUpdateEvent, value);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm[modelName]).toBe(value);
});
});
it(`${elementName} is ${disabledByToggle} by enabled set to false`, () => {
store.dispatch('updateSettings', { enabled: false });
const expectation = disabledByToggle === 'disabled' ? 'true' : undefined;
expect(findFormElements(elementName, formGroup).attributes('disabled')).toBe(expectation);
});
},
);
describe('form actions', () => {
let form; let form;
beforeEach(() => { beforeEach(() => {
form = findForm(); form = findForm();
}); });
describe('cancel button', () => { describe('data binding', () => {
it('has type reset', () => { it('v-model change update the settings property', () => {
expect(findCancelButton().attributes('type')).toBe('reset'); dispatchSpy.mockReturnValue();
}); form.vm.$emit('input', 'foo');
expect(dispatchSpy).toHaveBeenCalledWith('updateSettings', { settings: 'foo' });
it('is disabled the form was not changed from his original value', () => {
store.dispatch('receiveSettingsSuccess', { foo: 'bar' });
return wrapper.vm.$nextTick().then(() => {
expect(findCancelButton().attributes('disabled')).toBe('true');
});
});
it('is disabled when the form data is loading', () => {
store.dispatch('toggleLoading');
return wrapper.vm.$nextTick().then(() => {
expect(findCancelButton().attributes('disabled')).toBe('true');
});
});
it('is enabled when the user changed something in the form and the data is not being loaded', () => {
store.dispatch('receiveSettingsSuccess', { foo: 'bar' });
store.dispatch('updateSettings', { foo: 'baz' });
return wrapper.vm.$nextTick().then(() => {
expect(findCancelButton().attributes('disabled')).toBe(undefined);
});
}); });
}); });
describe('form cancel event', () => { describe('form reset event', () => {
it('calls the appropriate function', () => { it('calls the appropriate function', () => {
dispatchSpy.mockReturnValue(); dispatchSpy.mockReturnValue();
form.trigger('reset'); form.vm.$emit('reset');
expect(dispatchSpy).toHaveBeenCalledWith('resetSettings'); expect(dispatchSpy).toHaveBeenCalledWith('resetSettings');
}); });
it('tracks the reset event', () => { it('tracks the reset event', () => {
dispatchSpy.mockReturnValue(); dispatchSpy.mockReturnValue();
form.trigger('reset'); form.vm.$emit('reset');
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'reset_form', trackingPayload); expect(Tracking.event).toHaveBeenCalledWith(undefined, 'reset_form', trackingPayload);
}); });
}); });
it('save has type submit', () => {
expect(findSaveButton().attributes('type')).toBe('submit');
});
describe('when isLoading is true', () => {
beforeEach(() => {
store.dispatch('toggleLoading');
});
afterEach(() => {
store.dispatch('toggleLoading');
});
it.each`
elementName
${'toggle'}
${'interval'}
${'schedule'}
${'latest'}
${'name-matching'}
`(`${FORM_ELEMENTS_ID_PREFIX}-$elementName is disabled`, ({ elementName }) => {
expect(findFormElements(elementName).attributes('disabled')).toBe('true');
});
it('submit button is disabled and shows a spinner', () => {
const button = findSaveButton();
expect(button.attributes('disabled')).toBeTruthy();
expect(findLoadingIcon(button)).toExist();
});
it('cancel button is disabled', () => {
expect(findCancelButton().attributes('disabled')).toBeTruthy();
});
});
describe('form submit event ', () => { describe('form submit event ', () => {
it('calls the appropriate function', () => {
dispatchSpy.mockResolvedValue();
form.trigger('submit');
expect(dispatchSpy).toHaveBeenCalled();
});
it('dispatches the saveSettings action', () => { it('dispatches the saveSettings action', () => {
dispatchSpy.mockResolvedValue(); dispatchSpy.mockResolvedValue();
form.trigger('submit'); form.vm.$emit('submit');
expect(dispatchSpy).toHaveBeenCalledWith('saveSettings'); expect(dispatchSpy).toHaveBeenCalledWith('saveSettings');
}); });
it('tracks the submit event', () => { it('tracks the submit event', () => {
dispatchSpy.mockResolvedValue(); dispatchSpy.mockResolvedValue();
form.trigger('submit'); form.vm.$emit('submit');
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload); expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload);
}); });
it('show a success toast when submit succeed', () => { it('show a success toast when submit succeed', () => {
dispatchSpy.mockResolvedValue(); dispatchSpy.mockResolvedValue();
form.trigger('submit'); form.vm.$emit('submit');
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, { expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, {
type: 'success', type: 'success',
...@@ -232,7 +96,7 @@ describe('Settings Form', () => { ...@@ -232,7 +96,7 @@ describe('Settings Form', () => {
it('show an error toast when submit fails', () => { it('show an error toast when submit fails', () => {
dispatchSpy.mockRejectedValue(); dispatchSpy.mockRejectedValue();
form.trigger('submit'); form.vm.$emit('submit');
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, { expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, {
type: 'error', type: 'error',
...@@ -241,45 +105,4 @@ describe('Settings Form', () => { ...@@ -241,45 +105,4 @@ describe('Settings Form', () => {
}); });
}); });
}); });
describe('form validation', () => {
describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => {
const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(',');
beforeEach(() => {
store.dispatch('updateSettings', { name_regex: invalidString });
});
it('save btn is disabled', () => {
expect(findSaveButton().attributes('disabled')).toBeTruthy();
});
it('nameRegexState is false', () => {
expect(wrapper.vm.nameRegexState).toBe(false);
});
});
it('if the user did not type validation is null', () => {
store.dispatch('updateSettings', { name_regex: null });
expect(wrapper.vm.nameRegexState).toBe(null);
return wrapper.vm.$nextTick().then(() => {
expect(findSaveButton().attributes('disabled')).toBeFalsy();
});
});
it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => {
store.dispatch('updateSettings', { name_regex: 'abc' });
expect(wrapper.vm.nameRegexState).toBe(true);
});
});
describe('help text', () => {
it('toggleDescriptionText text reflects enabled property', () => {
const toggleHelpText = findFormGroup('toggle').find('span');
expect(toggleHelpText.html()).toContain('disabled');
wrapper.setData({ enabled: true });
return wrapper.vm.$nextTick().then(() => {
expect(toggleHelpText.html()).toContain('enabled');
});
});
});
}); });
...@@ -10,11 +10,14 @@ describe('Actions Registry Store', () => { ...@@ -10,11 +10,14 @@ describe('Actions Registry Store', () => {
${'updateSettings'} | ${types.UPDATE_SETTINGS} | ${'foo'} ${'updateSettings'} | ${types.UPDATE_SETTINGS} | ${'foo'}
${'toggleLoading'} | ${types.TOGGLE_LOADING} | ${undefined} ${'toggleLoading'} | ${types.TOGGLE_LOADING} | ${undefined}
${'resetSettings'} | ${types.RESET_SETTINGS} | ${undefined} ${'resetSettings'} | ${types.RESET_SETTINGS} | ${undefined}
`('%s action invokes %s mutation with payload %s', ({ actionName, mutationName, payload }) => { `(
it('should set the initial state', done => { '$actionName invokes $mutationName with payload $payload',
testAction(actions[actionName], payload, {}, [{ type: mutationName, payload }], [], done); ({ actionName, mutationName, payload }) => {
}); it('should set state', done => {
}); testAction(actions[actionName], payload, {}, [{ type: mutationName, payload }], [], done);
});
},
);
describe('receiveSettingsSuccess', () => { describe('receiveSettingsSuccess', () => {
it('calls SET_SETTINGS when data is present', () => { it('calls SET_SETTINGS when data is present', () => {
......
import * as getters from '~/registry/settings/store/getters'; import * as getters from '~/registry/settings/store/getters';
import * as utils from '~/registry/settings/utils'; import * as utils from '~/registry/shared/utils';
import { formOptions } from '../mock_data'; import { formOptions } from '../../shared/mock_data';
describe('Getters registry settings store', () => { describe('Getters registry settings store', () => {
const settings = { const settings = {
......
import mutations from '~/registry/settings/store/mutations'; import mutations from '~/registry/settings/store/mutations';
import * as types from '~/registry/settings/store/mutation_types'; import * as types from '~/registry/settings/store/mutation_types';
import createState from '~/registry/settings/store/state'; import createState from '~/registry/settings/store/state';
import { formOptions, stringifiedFormOptions } from '../mock_data'; import { formOptions, stringifiedFormOptions } from '../../shared/mock_data';
describe('Mutations Registry Store', () => { describe('Mutations Registry Store', () => {
let mockState; let mockState;
...@@ -28,7 +28,7 @@ describe('Mutations Registry Store', () => { ...@@ -28,7 +28,7 @@ describe('Mutations Registry Store', () => {
mockState.settings = { foo: 'bar' }; mockState.settings = { foo: 'bar' };
const payload = { foo: 'baz' }; const payload = { foo: 'baz' };
const expectedState = { ...mockState, settings: payload }; const expectedState = { ...mockState, settings: payload };
mutations[types.UPDATE_SETTINGS](mockState, payload); mutations[types.UPDATE_SETTINGS](mockState, { settings: payload });
expect(mockState.settings).toEqual(expectedState.settings); expect(mockState.settings).toEqual(expectedState.settings);
}); });
}); });
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Settings Form renders 1`] = ` exports[`Expiration Policy Form renders 1`] = `
<form> <form
class="lh-2"
>
<div <div
class="card" class="card"
> >
...@@ -56,7 +58,6 @@ exports[`Settings Form renders 1`] = ` ...@@ -56,7 +58,6 @@ exports[`Settings Form renders 1`] = `
<glformselect-stub <glformselect-stub
disabled="true" disabled="true"
id="expiration-policy-interval" id="expiration-policy-interval"
value="bar"
> >
<option <option
value="foo" value="foo"
...@@ -85,7 +86,6 @@ exports[`Settings Form renders 1`] = ` ...@@ -85,7 +86,6 @@ exports[`Settings Form renders 1`] = `
<glformselect-stub <glformselect-stub
disabled="true" disabled="true"
id="expiration-policy-schedule" id="expiration-policy-schedule"
value="bar"
> >
<option <option
value="foo" value="foo"
...@@ -114,7 +114,6 @@ exports[`Settings Form renders 1`] = ` ...@@ -114,7 +114,6 @@ exports[`Settings Form renders 1`] = `
<glformselect-stub <glformselect-stub
disabled="true" disabled="true"
id="expiration-policy-latest" id="expiration-policy-latest"
value="bar"
> >
<option <option
value="foo" value="foo"
...@@ -159,7 +158,6 @@ exports[`Settings Form renders 1`] = ` ...@@ -159,7 +158,6 @@ exports[`Settings Form renders 1`] = `
> >
<glbutton-stub <glbutton-stub
class="mr-2 d-block" class="mr-2 d-block"
disabled="true"
size="md" size="md"
type="reset" type="reset"
variant="secondary" variant="secondary"
......
import { mount } from '@vue/test-utils';
import stubChildren from 'helpers/stub_children';
import component from '~/registry/shared/components/expiration_policy_form.vue';
import { NAME_REGEX_LENGTH } from '~/registry/shared/constants';
import { formOptions } from '../mock_data';
describe('Expiration Policy Form', () => {
let wrapper;
const FORM_ELEMENTS_ID_PREFIX = '#expiration-policy';
const GlLoadingIcon = { name: 'gl-loading-icon-stub', template: '<svg></svg>' };
const findFormGroup = name => wrapper.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}-group`);
const findFormElements = (name, parent = wrapper) =>
parent.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}`);
const findCancelButton = () => wrapper.find({ ref: 'cancel-button' });
const findSaveButton = () => wrapper.find({ ref: 'save-button' });
const findForm = () => wrapper.find({ ref: 'form-element' });
const findLoadingIcon = (parent = wrapper) => parent.find(GlLoadingIcon);
const mountComponent = props => {
wrapper = mount(component, {
stubs: {
...stubChildren(component),
GlCard: false,
GlLoadingIcon,
},
propsData: {
formOptions,
...props,
},
methods: {
// override idGenerator to avoid having to test with dynamic uid
idGenerator: value => value,
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders', () => {
mountComponent();
expect(wrapper.element).toMatchSnapshot();
});
describe.each`
elementName | modelName | value | disabledByToggle
${'toggle'} | ${'enabled'} | ${true} | ${'not disabled'}
${'interval'} | ${'older_than'} | ${'foo'} | ${'disabled'}
${'schedule'} | ${'cadence'} | ${'foo'} | ${'disabled'}
${'latest'} | ${'keep_n'} | ${'foo'} | ${'disabled'}
${'name-matching'} | ${'name_regex'} | ${'foo'} | ${'disabled'}
`(
`${FORM_ELEMENTS_ID_PREFIX}-$elementName form element`,
({ elementName, modelName, value, disabledByToggle }) => {
it(`${elementName} form group exist in the dom`, () => {
mountComponent();
const formGroup = findFormGroup(elementName);
expect(formGroup.exists()).toBe(true);
});
it(`${elementName} form group has a label-for property`, () => {
mountComponent();
const formGroup = findFormGroup(elementName);
expect(formGroup.attributes('label-for')).toBe(`expiration-policy-${elementName}`);
});
it(`${elementName} form group has a label-cols property`, () => {
mountComponent({ labelCols: '1' });
const formGroup = findFormGroup(elementName);
return wrapper.vm.$nextTick().then(() => {
expect(formGroup.attributes('label-cols')).toBe('1');
});
});
it(`${elementName} form group has a label-align property`, () => {
mountComponent({ labelAlign: 'foo' });
const formGroup = findFormGroup(elementName);
return wrapper.vm.$nextTick().then(() => {
expect(formGroup.attributes('label-align')).toBe('foo');
});
});
it(`${elementName} form group contains an input element`, () => {
mountComponent();
const formGroup = findFormGroup(elementName);
expect(findFormElements(elementName, formGroup).exists()).toBe(true);
});
it(`${elementName} form element change updated ${modelName} with ${value}`, () => {
mountComponent();
const formGroup = findFormGroup(elementName);
const element = findFormElements(elementName, formGroup);
const modelUpdateEvent = element.vm.$options.model
? element.vm.$options.model.event
: 'input';
element.vm.$emit(modelUpdateEvent, value);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted('input')).toEqual([[{ [modelName]: value }]]);
});
});
it(`${elementName} is ${disabledByToggle} by enabled set to false`, () => {
mountComponent({ settings: { enabled: false } });
const formGroup = findFormGroup(elementName);
const expectation = disabledByToggle === 'disabled' ? 'true' : undefined;
expect(findFormElements(elementName, formGroup).attributes('disabled')).toBe(expectation);
});
},
);
describe('form actions', () => {
describe('cancel button', () => {
it('has type reset', () => {
mountComponent();
expect(findCancelButton().attributes('type')).toBe('reset');
});
it('is disabled when disableCancelButton is true', () => {
mountComponent({ disableCancelButton: true });
return wrapper.vm.$nextTick().then(() => {
expect(findCancelButton().attributes('disabled')).toBe('true');
});
});
it('is disabled isLoading is true', () => {
mountComponent({ isLoading: true });
return wrapper.vm.$nextTick().then(() => {
expect(findCancelButton().attributes('disabled')).toBe('true');
});
});
it('is enabled when isLoading and disableCancelButton are false', () => {
mountComponent({ disableCancelButton: false, isLoading: false });
return wrapper.vm.$nextTick().then(() => {
expect(findCancelButton().attributes('disabled')).toBe(undefined);
});
});
});
describe('form cancel event', () => {
it('calls the appropriate function', () => {
mountComponent();
findForm().trigger('reset');
expect(wrapper.emitted('reset')).toBeTruthy();
});
});
it('save has type submit', () => {
mountComponent();
expect(findSaveButton().attributes('type')).toBe('submit');
});
describe('when isLoading is true', () => {
beforeEach(() => {
mountComponent({ isLoading: true });
});
it.each`
elementName
${'toggle'}
${'interval'}
${'schedule'}
${'latest'}
${'name-matching'}
`(`${FORM_ELEMENTS_ID_PREFIX}-$elementName is disabled`, ({ elementName }) => {
expect(findFormElements(elementName).attributes('disabled')).toBe('true');
});
it('submit button is disabled and shows a spinner', () => {
const button = findSaveButton();
expect(button.attributes('disabled')).toBeTruthy();
expect(findLoadingIcon(button)).toExist();
});
});
describe('form submit event ', () => {
it('calls the appropriate function', () => {
mountComponent();
findForm().trigger('submit');
expect(wrapper.emitted('submit')).toBeTruthy();
});
});
});
describe('form validation', () => {
describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => {
const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(',');
beforeEach(() => {
mountComponent({ value: { name_regex: invalidString } });
});
it('save btn is disabled', () => {
expect(findSaveButton().attributes('disabled')).toBeTruthy();
});
it('nameRegexState is false', () => {
expect(wrapper.vm.nameRegexState).toBe(false);
});
});
it('if the user did not type validation is null', () => {
mountComponent({ value: { name_regex: '' } });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.nameRegexState).toBe(null);
expect(findSaveButton().attributes('disabled')).toBeFalsy();
});
});
it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => {
mountComponent({ value: { name_regex: 'foo' } });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.nameRegexState).toBe(true);
});
});
});
describe('help text', () => {
it('toggleDescriptionText show disabled when settings.enabled is false', () => {
mountComponent();
const toggleHelpText = findFormGroup('toggle').find('span');
expect(toggleHelpText.html()).toContain('disabled');
});
it('toggleDescriptionText show enabled when settings.enabled is true', () => {
mountComponent({ value: { enabled: true } });
const toggleHelpText = findFormGroup('toggle').find('span');
expect(toggleHelpText.html()).toContain('enabled');
});
});
});
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