Commit 835d4a5a authored by Lukas Eipert's avatar Lukas Eipert

Implementation of Override Form in Integrations

This properly exposes the admin settings to the frontend components if
they exist and allows the user to switch between them and custom
settings.

If the user chooses to use the admin settings, all form fields are
populated as disabled or readonly.
Co-authored-by: default avatarJustin Ho Tuan Duong <hduong@gitlab.com>
parent 9793e093
......@@ -23,7 +23,7 @@ export default {
};
},
computed: {
...mapGetters(['disableForm']),
...mapGetters(['isInheriting']),
},
mounted() {
// Initialize view
......@@ -46,7 +46,7 @@ export default {
v-model="activated"
name="service[active]"
class="gl-display-block gl-line-height-0"
:disabled="disableForm"
:disabled="isInheriting"
@change="onToggle"
/>
</gl-form-group>
......@@ -55,7 +55,12 @@ export default {
<div class="form-group row" role="group">
<label for="service[active]" class="col-form-label col-sm-2">{{ __('Active') }}</label>
<div class="col-sm-10 pt-1">
<gl-toggle v-model="activated" name="service[active]" @change="onToggle" />
<gl-toggle
v-model="activated"
name="service[active]"
:disabled="isInheriting"
@change="onToggle"
/>
</div>
</div>
</div>
......
......@@ -60,7 +60,7 @@ export default {
};
},
computed: {
...mapGetters(['disableForm']),
...mapGetters(['isInheriting']),
isCheckbox() {
return this.type === 'checkbox';
},
......@@ -109,7 +109,7 @@ export default {
id: this.fieldId,
name: this.fieldName,
state: this.valid,
disabled: this.disableForm,
readonly: this.isInheriting,
};
},
valid() {
......@@ -145,12 +145,15 @@ export default {
</template>
<template v-if="isCheckbox">
<input :name="fieldName" type="hidden" value="false" />
<gl-form-checkbox v-model="model" v-bind="sharedProps">
<input :name="fieldName" type="hidden" :value="model || false" />
<gl-form-checkbox :id="fieldId" v-model="model" :disabled="isInheriting">
{{ humanizedTitle }}
</gl-form-checkbox>
</template>
<gl-form-select v-else-if="isSelect" v-model="model" v-bind="sharedProps" :options="options" />
<template v-else-if="isSelect">
<input type="hidden" :name="fieldName" :value="model" />
<gl-form-select :id="fieldId" v-model="model" :options="options" :disabled="isInheriting" />
</template>
<gl-form-textarea
v-else-if="isTextarea"
v-model="model"
......
<script>
import { mapState } from 'vuex';
import { mapState, mapActions, mapGetters } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import OverrideDropdown from './override_dropdown.vue';
......@@ -20,57 +20,55 @@ export default {
DynamicField,
},
mixins: [glFeatureFlagsMixin()],
props: {
activeToggleProps: {
type: Object,
required: true,
},
showActive: {
type: Boolean,
required: true,
},
triggerFieldsProps: {
type: Object,
required: true,
},
jiraIssuesProps: {
type: Object,
required: true,
},
triggerEvents: {
type: Array,
required: false,
default: () => [],
},
fields: {
type: Array,
required: false,
default: () => [],
},
type: {
type: String,
required: true,
},
},
computed: {
...mapState(['overrideAvailable']),
...mapGetters(['currentKey', 'propsSource']),
...mapState(['adminState', 'override']),
isJira() {
return this.type === 'jira';
return this.propsSource.type === 'jira';
},
showJiraIssuesFields() {
return this.isJira && this.glFeatures.jiraIssuesIntegration;
},
},
methods: {
...mapActions(['setOverride']),
},
};
</script>
<template>
<div>
<override-dropdown v-if="overrideAvailable" />
<active-toggle v-if="showActive" v-bind="activeToggleProps" />
<jira-trigger-fields v-if="isJira" v-bind="triggerFieldsProps" />
<trigger-fields v-else-if="triggerEvents.length" :events="triggerEvents" :type="type" />
<dynamic-field v-for="field in fields" :key="field.name" v-bind="field" />
<jira-issues-fields v-if="showJiraIssuesFields" v-bind="jiraIssuesProps" />
<override-dropdown
v-if="adminState !== null"
:inherit-from-id="adminState.id"
:override="override"
@change="setOverride"
/>
<active-toggle
v-if="propsSource.showActive"
:key="`${currentKey}-active-toggle`"
v-bind="propsSource.activeToggleProps"
/>
<jira-trigger-fields
v-if="isJira"
:key="`${currentKey}-jira-trigger-fields`"
v-bind="propsSource.triggerFieldsProps"
/>
<trigger-fields
v-else-if="propsSource.triggerEvents.length"
:key="`${currentKey}-trigger-fields`"
:events="propsSource.triggerEvents"
:type="propsSource.type"
/>
<dynamic-field
v-for="field in propsSource.fields"
:key="`${currentKey}-${field.name}`"
v-bind="field"
/>
<jira-issues-fields
v-if="showJiraIssuesFields"
:key="`${currentKey}-jira-issues-fields`"
v-bind="propsSource.jiraIssuesProps"
/>
</div>
</template>
......@@ -89,8 +89,8 @@ export default {
}}
</p>
<template v-if="showJiraIssuesIntegration">
<input name="service[issues_enabled]" type="hidden" value="false" />
<gl-form-checkbox v-model="enableJiraIssues" name="service[issues_enabled]">
<input name="service[issues_enabled]" type="hidden" :value="enableJiraIssues || false" />
<gl-form-checkbox v-model="enableJiraIssues">
{{ s__('JiraService|Enable Jira issues') }}
<template #help>
{{
......
......@@ -56,7 +56,7 @@ export default {
};
},
computed: {
...mapGetters(['disableForm']),
...mapGetters(['isInheriting']),
showEnableComments() {
return this.triggerCommit || this.triggerMergeRequest;
},
......@@ -75,21 +75,17 @@ export default {
)
"
>
<input name="service[commit_events]" type="hidden" value="false" />
<gl-form-checkbox
v-model="triggerCommit"
name="service[commit_events]"
:disabled="disableForm"
>
<input name="service[commit_events]" type="hidden" :value="triggerCommit || false" />
<gl-form-checkbox v-model="triggerCommit" :disabled="isInheriting">
{{ __('Commit') }}
</gl-form-checkbox>
<input name="service[merge_requests_events]" type="hidden" value="false" />
<gl-form-checkbox
v-model="triggerMergeRequest"
<input
name="service[merge_requests_events]"
:disabled="disableForm"
>
type="hidden"
:value="triggerMergeRequest || false"
/>
<gl-form-checkbox v-model="triggerMergeRequest" :disabled="isInheriting">
{{ __('Merge request') }}
</gl-form-checkbox>
</gl-form-group>
......@@ -99,12 +95,12 @@ export default {
:label="s__('Integrations|Comment settings:')"
data-testid="comment-settings"
>
<input name="service[comment_on_event_enabled]" type="hidden" value="false" />
<gl-form-checkbox
v-model="enableComments"
<input
name="service[comment_on_event_enabled]"
:disabled="disableForm"
>
type="hidden"
:value="enableComments || false"
/>
<gl-form-checkbox v-model="enableComments" :disabled="isInheriting">
{{ s__('Integrations|Enable comments') }}
</gl-form-checkbox>
</gl-form-group>
......@@ -114,13 +110,18 @@ export default {
:label="s__('Integrations|Comment detail:')"
data-testid="comment-detail"
>
<input
v-if="isInheriting"
name="service[comment_detail]"
type="hidden"
:value="commentDetail"
/>
<gl-form-radio
v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value"
v-model="commentDetail"
:value="commentDetailOption.value"
name="service[comment_detail]"
:disabled="disableForm"
:disabled="isInheriting"
>
{{ commentDetailOption.label }}
<template #help>
......@@ -141,13 +142,17 @@ export default {
}}
</label>
<input name="service[commit_events]" type="hidden" value="false" />
<gl-form-checkbox v-model="triggerCommit" name="service[commit_events]">
<input name="service[commit_events]" type="hidden" :value="triggerCommit || false" />
<gl-form-checkbox v-model="triggerCommit" :disabled="isInheriting">
{{ __('Commit') }}
</gl-form-checkbox>
<input name="service[merge_requests_events]" type="hidden" value="false" />
<gl-form-checkbox v-model="triggerMergeRequest" name="service[merge_requests_events]">
<input
name="service[merge_requests_events]"
type="hidden"
:value="triggerMergeRequest || false"
/>
<gl-form-checkbox v-model="triggerMergeRequest" :disabled="isInheriting">
{{ __('Merge request') }}
</gl-form-checkbox>
......@@ -159,8 +164,12 @@ export default {
<label>
{{ s__('Integrations|Comment settings:') }}
</label>
<input name="service[comment_on_event_enabled]" type="hidden" value="false" />
<gl-form-checkbox v-model="enableComments" name="service[comment_on_event_enabled]">
<input
name="service[comment_on_event_enabled]"
type="hidden"
:value="enableComments || false"
/>
<gl-form-checkbox v-model="enableComments" :disabled="isInheriting">
{{ s__('Integrations|Enable comments') }}
</gl-form-checkbox>
......@@ -168,12 +177,18 @@ export default {
<label>
{{ s__('Integrations|Comment detail:') }}
</label>
<input
v-if="isInheriting"
name="service[comment_detail]"
type="hidden"
:value="commentDetail"
/>
<gl-form-radio
v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value"
v-model="commentDetail"
:value="commentDetailOption.value"
name="service[comment_detail]"
:disabled="isInheriting"
>
{{ commentDetailOption.label }}
<template #help>
......
<script>
import { s__ } from '~/locale';
import { mapState } from 'vuex';
import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
const options = [
const dropdownOptions = [
{
value: 'instance',
value: false,
text: s__('Integrations|Use instance level settings'),
},
{
value: 'project',
value: true,
text: s__('Integrations|Use custom settings'),
},
];
const defaultOption = options[0];
const customOption = options[1];
export default {
dropdownOptions,
name: 'OverrideDropdown',
components: {
GlNewDropdown,
GlNewDropdownItem,
},
props: {},
props: {
inheritFromId: {
type: Number,
required: true,
},
override: {
type: Boolean,
required: true,
},
},
data() {
return {
options,
selected: defaultOption,
selected: dropdownOptions.find(x => x.value === this.override),
};
},
computed: {
...mapState(['override']),
},
methods: {
onClick(option) {
this.selected = option;
this.$store.dispatch('setOverride', option === customOption);
this.$emit('change', option.value);
},
},
};
......@@ -47,8 +49,13 @@ export default {
class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-py-4 gl-mt-5 gl-mb-6 gl-border-t-1 gl-border-t-solid gl-border-b-1 gl-border-b-solid gl-border-gray-100"
>
<span>{{ s__('Integrations|This integration has multiple settings available.') }}</span>
<input name="service[inherit_from_id]" :value="override ? '' : inheritFromId" type="hidden" />
<gl-new-dropdown :text="selected.text">
<gl-new-dropdown-item v-for="option in options" :key="option.value" @click="onClick(option)">
<gl-new-dropdown-item
v-for="option in $options.dropdownOptions"
:key="option.value"
@click="onClick(option)"
>
{{ option.text }}
</gl-new-dropdown-item>
</gl-new-dropdown>
......
......@@ -33,7 +33,7 @@ export default {
},
},
computed: {
...mapGetters(['disableForm']),
...mapGetters(['isInheriting']),
placeholder() {
return placeholderForType[this.type];
},
......@@ -59,12 +59,8 @@ export default {
>
<div id="trigger-fields" class="gl-pt-3">
<gl-form-group v-for="event in events" :key="event.title" :description="event.description">
<input :name="checkboxName(event.name)" type="hidden" value="false" />
<gl-form-checkbox
v-model="event.value"
:name="checkboxName(event.name)"
:disabled="disableForm"
>
<input :name="checkboxName(event.name)" type="hidden" :value="event.value || false" />
<gl-form-checkbox v-model="event.value" :disabled="isInheriting">
{{ startCase(event.title) }}
</gl-form-checkbox>
<gl-form-input
......@@ -72,7 +68,7 @@ export default {
v-model="event.field.value"
:name="fieldName(event.field.name)"
:placeholder="placeholder"
:disabled="disableForm"
:readonly="isInheriting"
/>
</gl-form-group>
</div>
......
import Vue from 'vue';
import { isEmpty } from 'lodash';
import { createStore } from './store';
import { parseBoolean } from '~/lib/utils/common_utils';
import IntegrationForm from './components/integration_form.vue';
export default el => {
if (!el) {
return null;
}
function parseBooleanInData(data) {
const result = {};
Object.entries(data).forEach(([key, value]) => {
result[key] = parseBoolean(value);
});
return result;
}
function parseBooleanInData(data) {
const result = {};
Object.entries(data).forEach(([key, value]) => {
result[key] = parseBoolean(value);
});
return result;
}
function parseDatasetToProps(data) {
const {
id,
type,
commentDetail,
projectKey,
......@@ -27,7 +23,7 @@ export default el => {
fields,
inheritFromId,
...booleanAttributes
} = el.dataset;
} = data;
const {
showActive,
activated,
......@@ -38,38 +34,53 @@ export default el => {
enableJiraIssues,
} = parseBooleanInData(booleanAttributes);
return {
activeToggleProps: {
initialActivated: activated,
},
showActive,
type,
triggerFieldsProps: {
initialTriggerCommit: commitEvents,
initialTriggerMergeRequest: mergeRequestEvents,
initialEnableComments: enableComments,
initialCommentDetail: commentDetail,
},
jiraIssuesProps: {
showJiraIssuesIntegration,
initialEnableJiraIssues: enableJiraIssues,
initialProjectKey: projectKey,
upgradePlanPath,
editProjectPath,
},
triggerEvents: JSON.parse(triggerEvents),
fields: JSON.parse(fields),
inheritFromId: parseInt(inheritFromId, 10),
id: parseInt(id, 10),
};
}
export default (el, adminEl) => {
if (!el) {
return null;
}
const props = parseDatasetToProps(el.dataset);
const initialState = {
overrideAvailable: !isEmpty(inheritFromId),
adminState: null,
customState: props,
};
if (adminEl) {
initialState.adminState = Object.freeze(parseDatasetToProps(adminEl.dataset));
}
return new Vue({
el,
store: createStore(initialState),
render(createElement) {
return createElement(IntegrationForm, {
props: {
activeToggleProps: {
initialActivated: activated,
},
showActive,
type,
triggerFieldsProps: {
initialTriggerCommit: commitEvents,
initialTriggerMergeRequest: mergeRequestEvents,
initialEnableComments: enableComments,
initialCommentDetail: commentDetail,
},
jiraIssuesProps: {
showJiraIssuesIntegration,
initialEnableJiraIssues: enableJiraIssues,
initialProjectKey: projectKey,
upgradePlanPath,
editProjectPath,
},
triggerEvents: JSON.parse(triggerEvents),
fields: JSON.parse(fields),
},
});
return createElement(IntegrationForm);
},
});
};
import * as types from './mutation_types';
// eslint-disable-next-line import/prefer-default-export
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
export const setOverrideAvailable = ({ commit }, overrideAvailable) =>
commit(types.SET_OVERRIDE_AVAILABLE, overrideAvailable);
// eslint-disable-next-line import/prefer-default-export
export const disableForm = state => state.overrideAvailable && !state.override;
export const isInheriting = state => (state.adminState === null ? false : !state.override);
export const propsSource = (state, getters) =>
getters.isInheriting ? state.adminState : state.customState;
export const currentKey = (state, getters) => (getters.isInheriting ? 'admin' : 'custom');
......@@ -7,6 +7,7 @@ import createState from './state';
Vue.use(Vuex);
// eslint-disable-next-line import/prefer-default-export
export const createStore = (initialState = {}) =>
new Vuex.Store({
actions,
......@@ -14,5 +15,3 @@ export const createStore = (initialState = {}) =>
mutations,
state: createState(initialState),
});
export default createStore();
// eslint-disable-next-line import/prefer-default-export
export const SET_OVERRIDE = 'SET_OVERRIDE';
export const SET_OVERRIDE_AVAILABLE = 'SET_OVERRIDE_AVAILABLE';
......@@ -4,8 +4,4 @@ export default {
[types.SET_OVERRIDE](state, override) {
state.override = override;
},
[types.SET_OVERRIDE_AVAILABLE](state, overrideAvailable) {
state.overrideAvailable = overrideAvailable;
},
};
export default ({ overrideAvailable = false }) => ({
override: false,
overrideAvailable,
});
export default ({ adminState = null, customState = {} } = {}) => {
const override = adminState !== null ? adminState.id !== customState.inheritFromId : false;
return {
override,
adminState,
customState,
};
};
......@@ -22,7 +22,10 @@ export default class IntegrationSettingsForm {
init() {
// Init Vue component
initForm(document.querySelector('.js-vue-integration-settings'));
initForm(
document.querySelector('.js-vue-integration-settings'),
document.querySelector('.js-vue-admin-integration-settings'),
);
eventHub.$on('toggle', active => {
this.formActive = active;
this.handleServiceToggle();
......
......@@ -8,6 +8,8 @@
= markdown integration.help
.service-settings
- if @admin_integration
.js-vue-admin-integration-settings{ data: integration_form_data(@admin_integration) }
.js-vue-integration-settings{ data: integration_form_data(integration) }
- if show_service_trigger_events?(integration)
......
......@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Disable individual triggers', :js do
include_context 'project service activation'
let(:checkbox_selector) { 'input[type=checkbox][name$="_events]"]' }
let(:checkbox_selector) { 'input[name$="_events]"]' }
before do
visit_project_integration(service_name)
......@@ -18,7 +18,7 @@ RSpec.describe 'Disable individual triggers', :js do
event_count = HipchatService.supported_events.count
expect(page).to have_content "Trigger"
expect(page).to have_css(checkbox_selector, count: event_count)
expect(page).to have_css(checkbox_selector, visible: :all, count: event_count)
end
end
......@@ -27,7 +27,7 @@ RSpec.describe 'Disable individual triggers', :js do
it "doesn't show unnecessary Trigger checkboxes" do
expect(page).not_to have_content "Trigger"
expect(page).not_to have_css(checkbox_selector)
expect(page).not_to have_css(checkbox_selector, visible: :all)
end
end
end
import { mount } from '@vue/test-utils';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import { GlToggle } from '@gitlab/ui';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
const GL_TOGGLE_ACTIVE_CLASS = 'is-checked';
const GL_TOGGLE_DISABLED_CLASS = 'is-disabled';
describe('ActiveToggle', () => {
let wrapper;
......@@ -11,9 +13,12 @@ describe('ActiveToggle', () => {
initialActivated: true,
};
const createComponent = props => {
const createComponent = (props = {}, isInheriting = false) => {
wrapper = mount(ActiveToggle, {
propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
});
};
......@@ -29,6 +34,15 @@ describe('ActiveToggle', () => {
const findInputInToggle = () => findGlToggle().find('input');
describe('template', () => {
describe('is inheriting adminSettings', () => {
it('renders GlToggle as disabled', () => {
createComponent({}, true);
expect(findGlToggle().exists()).toBe(true);
expect(findButtonInToggle().classes()).toContain(GL_TOGGLE_DISABLED_CLASS);
});
});
describe('initialActivated is false', () => {
it('renders GlToggle as inactive', () => {
createComponent({
......
import { mount } from '@vue/test-utils';
import { createStore } from '~/integrations/edit/store';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
import { GlFormGroup, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormTextarea } from '@gitlab/ui';
......@@ -15,10 +14,12 @@ describe('DynamicField', () => {
value: '1',
};
const createComponent = props => {
const createComponent = (props, isInheriting = false) => {
wrapper = mount(DynamicField, {
propsData: { ...defaultProps, ...props },
store: createStore(),
computed: {
isInheriting: () => isInheriting,
},
});
};
......@@ -36,108 +37,143 @@ describe('DynamicField', () => {
const findGlFormTextarea = () => wrapper.find(GlFormTextarea);
describe('template', () => {
describe('dynamic field', () => {
describe('type is checkbox', () => {
beforeEach(() => {
createComponent({
type: 'checkbox',
describe.each([[true, 'disabled', 'readonly'], [false, undefined, undefined]])(
'dynamic field, when isInheriting = `%p`',
(isInheriting, disabled, readonly) => {
describe('type is checkbox', () => {
beforeEach(() => {
createComponent(
{
type: 'checkbox',
},
isInheriting,
);
});
});
it('renders GlFormCheckbox', () => {
expect(findGlFormCheckbox().exists()).toBe(true);
});
it(`renders GlFormCheckbox, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
expect(findGlFormCheckbox().exists()).toBe(true);
expect(
findGlFormCheckbox()
.find('[type=checkbox]')
.attributes('disabled'),
).toBe(disabled);
});
it('does not render other types of input', () => {
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
});
});
});
describe('type is select', () => {
beforeEach(() => {
createComponent({
type: 'select',
choices: [['all', 'All details'], ['standard', 'Standard']],
describe('type is select', () => {
beforeEach(() => {
createComponent(
{
type: 'select',
choices: [['all', 'All details'], ['standard', 'Standard']],
},
isInheriting,
);
});
});
it('renders findGlFormSelect', () => {
expect(findGlFormSelect().exists()).toBe(true);
expect(findGlFormSelect().findAll('option')).toHaveLength(2);
});
it(`renders GlFormSelect, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
expect(findGlFormSelect().exists()).toBe(true);
expect(findGlFormSelect().findAll('option')).toHaveLength(2);
expect(
findGlFormSelect()
.find('select')
.attributes('disabled'),
).toBe(disabled);
});
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
});
});
});
describe('type is textarea', () => {
beforeEach(() => {
createComponent({
type: 'textarea',
describe('type is textarea', () => {
beforeEach(() => {
createComponent(
{
type: 'textarea',
},
isInheriting,
);
});
});
it('renders findGlFormTextarea', () => {
expect(findGlFormTextarea().exists()).toBe(true);
});
it(`renders GlFormTextarea, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
expect(findGlFormTextarea().exists()).toBe(true);
expect(
findGlFormTextarea()
.find('textarea')
.attributes('readonly'),
).toBe(readonly);
});
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
});
});
});
describe('type is password', () => {
beforeEach(() => {
createComponent({
type: 'password',
describe('type is password', () => {
beforeEach(() => {
createComponent(
{
type: 'password',
},
isInheriting,
);
});
});
it('renders GlFormInput', () => {
expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes('type')).toBe('password');
});
it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes('type')).toBe('password');
expect(findGlFormInput().attributes('readonly')).toBe(readonly);
});
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
});
});
});
describe('type is text', () => {
beforeEach(() => {
createComponent({
type: 'text',
required: true,
describe('type is text', () => {
beforeEach(() => {
createComponent(
{
type: 'text',
required: true,
},
isInheriting,
);
});
});
it('renders GlFormInput', () => {
expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes()).toMatchObject({
type: 'text',
id: 'service_project_url',
name: 'service[project_url]',
placeholder: defaultProps.placeholder,
required: 'required',
it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes()).toMatchObject({
type: 'text',
id: 'service_project_url',
name: 'service[project_url]',
placeholder: defaultProps.placeholder,
required: 'required',
});
expect(findGlFormInput().attributes('readonly')).toBe(readonly);
});
});
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
});
});
});
});
},
);
describe('help text', () => {
it('renders description with help text', () => {
......
import { shallowMount } from '@vue/test-utils';
import { createStore } from '~/integrations/edit/store';
import IntegrationForm from '~/integrations/edit/components/integration_form.vue';
import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
import { mockIntegrationProps } from 'jest/integrations/edit/mock_data';
describe('IntegrationForm', () => {
let wrapper;
const defaultProps = {
activeToggleProps: {
initialActivated: true,
},
showActive: true,
triggerFieldsProps: {
initialTriggerCommit: false,
initialTriggerMergeRequest: false,
initialEnableComments: false,
},
jiraIssuesProps: {},
type: '',
};
const createComponent = (props, featureFlags = {}) => {
const createComponent = (customStateProps = {}, featureFlags = {}, initialState = {}) => {
wrapper = shallowMount(IntegrationForm, {
propsData: { ...defaultProps, ...props },
store: createStore(),
propsData: {},
store: createStore({
customState: { ...mockIntegrationProps, ...customStateProps },
...initialState,
}),
stubs: {
OverrideDropdown,
ActiveToggle,
JiraTriggerFields,
TriggerFields,
},
provide: {
glFeatures: featureFlags,
......@@ -45,6 +38,7 @@ describe('IntegrationForm', () => {
}
});
const findOverrideDropdown = () => wrapper.find(OverrideDropdown);
const findActiveToggle = () => wrapper.find(ActiveToggle);
const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields);
const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields);
......@@ -142,5 +136,35 @@ describe('IntegrationForm', () => {
});
});
});
describe('adminState state is null', () => {
it('does not render OverrideDropdown', () => {
createComponent(
{},
{},
{
adminState: null,
},
);
expect(findOverrideDropdown().exists()).toBe(false);
});
});
describe('adminState state is an object', () => {
it('renders OverrideDropdown', () => {
createComponent(
{},
{},
{
adminState: {
...mockIntegrationProps,
},
},
);
expect(findOverrideDropdown().exists()).toBe(true);
});
});
});
});
import { mount } from '@vue/test-utils';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
describe('JiraIssuesFields', () => {
let wrapper;
......
import { mount } from '@vue/test-utils';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import { GlFormCheckbox } from '@gitlab/ui';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
describe('JiraTriggerFields', () => {
let wrapper;
......@@ -11,9 +11,12 @@ describe('JiraTriggerFields', () => {
initialEnableComments: false,
};
const createComponent = props => {
const createComponent = (props, isInheriting = false) => {
wrapper = mount(JiraTriggerFields, {
propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
});
};
......@@ -93,5 +96,23 @@ describe('JiraTriggerFields', () => {
expect(findCommentDetail().isVisible()).toBe(true);
});
});
it('disables checkboxes and radios if inheriting', () => {
createComponent(
{
initialTriggerCommit: true,
initialEnableComments: true,
},
true,
);
wrapper.findAll('[type=checkbox]').wrappers.forEach(checkbox => {
expect(checkbox.attributes('disabled')).toBe('disabled');
});
wrapper.findAll('[type=radio]').wrappers.forEach(radio => {
expect(radio.attributes('disabled')).toBe('disabled');
});
});
});
});
import { mount } from '@vue/test-utils';
import { createStore } from '~/integrations/edit/store';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import { GlFormGroup, GlFormCheckbox, GlFormInput } from '@gitlab/ui';
......@@ -10,10 +9,12 @@ describe('TriggerFields', () => {
type: 'slack',
};
const createComponent = props => {
const createComponent = (props, isInheriting = false) => {
wrapper = mount(TriggerFields, {
propsData: { ...defaultProps, ...props },
store: createStore(),
computed: {
isInheriting: () => isInheriting,
},
});
};
......@@ -24,10 +25,11 @@ describe('TriggerFields', () => {
}
});
const findAllGlFormGroups = () => wrapper.find('#trigger-fields').findAll(GlFormGroup);
const findAllGlFormCheckboxes = () => wrapper.findAll(GlFormCheckbox);
const findAllGlFormInputs = () => wrapper.findAll(GlFormInput);
describe('template', () => {
describe.each([true, false])('template, isInheriting = `%p`', isInheriting => {
it('renders a label with text "Trigger"', () => {
createComponent();
......@@ -53,9 +55,12 @@ describe('TriggerFields', () => {
];
beforeEach(() => {
createComponent({
events,
});
createComponent(
{
events,
},
isInheriting,
);
});
it('does not render GlFormInput for each event', () => {
......@@ -71,8 +76,10 @@ describe('TriggerFields', () => {
});
});
it('renders GlFormCheckbox for each event', () => {
const checkboxes = findAllGlFormCheckboxes();
it(`renders GlFormCheckbox and corresponding hidden input for each event, which ${
isInheriting ? 'is' : 'is not'
} disabled`, () => {
const checkboxes = findAllGlFormGroups();
const expectedResults = [
{ labelText: 'Push', inputName: 'service[push_event]' },
{ labelText: 'Merge Request', inputName: 'service[merge_requests_event]' },
......@@ -80,14 +87,22 @@ describe('TriggerFields', () => {
expect(checkboxes).toHaveLength(2);
checkboxes.wrappers.forEach((checkbox, index) => {
const checkBox = checkbox.find(GlFormCheckbox);
expect(checkbox.find('label').text()).toBe(expectedResults[index].labelText);
expect(checkbox.find('input').attributes('name')).toBe(expectedResults[index].inputName);
expect(checkbox.vm.$attrs.checked).toBe(events[index].value);
expect(checkbox.find('[type=hidden]').attributes('name')).toBe(
expectedResults[index].inputName,
);
expect(checkbox.find('[type=hidden]').attributes('value')).toBe(
events[index].value.toString(),
);
expect(checkBox.vm.$attrs.disabled).toBe(isInheriting);
expect(checkBox.vm.$attrs.checked).toBe(events[index].value);
});
});
});
describe('events with field property', () => {
describe('events with field property, isInheriting = `%p`', () => {
const events = [
{
field: {
......@@ -104,16 +119,21 @@ describe('TriggerFields', () => {
];
beforeEach(() => {
createComponent({
events,
});
createComponent(
{
events,
},
isInheriting,
);
});
it('renders GlFormCheckbox for each event', () => {
expect(findAllGlFormCheckboxes()).toHaveLength(2);
});
it('renders GlFormInput for each event', () => {
it(`renders GlFormInput for each event, which ${
isInheriting ? 'is' : 'is not'
} readonly`, () => {
const fields = findAllGlFormInputs();
const expectedResults = [
{
......@@ -130,6 +150,7 @@ describe('TriggerFields', () => {
fields.wrappers.forEach((field, index) => {
expect(field.attributes()).toMatchObject(expectedResults[index]);
expect(field.vm.$attrs.readonly).toBe(isInheriting);
expect(field.vm.$attrs.value).toBe(events[index].field.value);
});
});
......
// eslint-disable-next-line import/prefer-default-export
export const mockIntegrationProps = {
id: 25,
activeToggleProps: {
initialActivated: true,
},
showActive: true,
triggerFieldsProps: {
initialTriggerCommit: false,
initialTriggerMergeRequest: false,
initialEnableComments: false,
},
jiraIssuesProps: {},
triggerEvents: [],
fields: [],
type: '',
inheritFromId: 25,
};
import createState from '~/integrations/edit/store/state';
import { setOverride } from '~/integrations/edit/store/actions';
import * as types from '~/integrations/edit/store/mutation_types';
import testAction from 'helpers/vuex_action_helper';
describe('Integration form store actions', () => {
let state;
beforeEach(() => {
state = createState();
});
describe('setOverride', () => {
it('should commit override mutation', () => {
return testAction(setOverride, true, state, [{ type: types.SET_OVERRIDE, payload: true }]);
});
});
});
import { currentKey, isInheriting, propsSource } from '~/integrations/edit/store/getters';
import createState from '~/integrations/edit/store/state';
import { mockIntegrationProps } from '../mock_data';
describe('Integration form store getters', () => {
let state;
const customState = { ...mockIntegrationProps, type: 'CustomState' };
const adminState = { ...mockIntegrationProps, type: 'AdminState' };
beforeEach(() => {
state = createState({ customState });
});
describe('isInheriting', () => {
describe('when adminState is null', () => {
it('returns false', () => {
expect(isInheriting(state)).toBe(false);
});
});
describe('when adminState is an object', () => {
beforeEach(() => {
state.adminState = adminState;
});
describe('when override is false', () => {
beforeEach(() => {
state.override = false;
});
it('returns false', () => {
expect(isInheriting(state)).toBe(true);
});
});
describe('when override is true', () => {
beforeEach(() => {
state.override = true;
});
it('returns true', () => {
expect(isInheriting(state)).toBe(false);
});
});
});
});
describe('propsSource', () => {
beforeEach(() => {
state.adminState = adminState;
});
it('equals adminState if inheriting', () => {
expect(propsSource(state, { isInheriting: true })).toEqual(adminState);
});
it('equals customState if not inheriting', () => {
expect(propsSource(state, { isInheriting: false })).toEqual(customState);
});
});
describe('currentKey', () => {
it('equals `admin` if inheriting', () => {
expect(currentKey(state, { isInheriting: true })).toEqual('admin');
});
it('equals `custom` if not inheriting', () => {
expect(currentKey(state, { isInheriting: false })).toEqual('custom');
});
});
});
import mutations from '~/integrations/edit/store/mutations';
import createState from '~/integrations/edit/store/state';
import * as types from '~/integrations/edit/store/mutation_types';
describe('Integration form store mutations', () => {
let state;
beforeEach(() => {
state = createState();
});
describe(`${types.SET_OVERRIDE}`, () => {
it('sets override', () => {
mutations[types.SET_OVERRIDE](state, true);
expect(state.override).toBe(true);
});
});
});
import createState from '~/integrations/edit/store/state';
describe('Integration form state factory', () => {
it('states default to null', () => {
expect(createState()).toEqual({
adminState: null,
customState: {},
override: false,
});
});
describe('override is initialized correctly', () => {
it.each([
[{ id: 25 }, { inheritFromId: null }, true],
[{ id: 25 }, { inheritFromId: 27 }, true],
[{ id: 25 }, { inheritFromId: 25 }, false],
[null, { inheritFromId: null }, false],
[null, { inheritFromId: 25 }, false],
])(
'for adminState: %p, customState: %p: override = `%p`',
(adminState, customState, expected) => {
expect(createState({ adminState, customState }).override).toEqual(expected);
},
);
});
});
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