Commit a558f7f8 authored by Robert Hunt's avatar Robert Hunt Committed by Natalia Tepluhina

Update RuleForm inputs to use GlFormInput and GlFormGroup

parent 41d18ebb
<script>
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
import { groupBy, isNumber } from 'lodash';
import { mapState, mapActions } from 'vuex';
import { isSafeURL } from '~/lib/utils/url_utility';
......@@ -27,10 +28,12 @@ function mapServerResponseToValidationErrors(messages) {
export default {
components: {
ApproverTypeSelect,
ApproversList,
ApproversSelect,
BranchesSelect,
ApproverTypeSelect,
GlFormGroup,
GlFormInput,
},
mixins: [glFeatureFlagsMixin()],
props: {
......@@ -95,7 +98,9 @@ export default {
invalidApprovalGateUrl() {
if (this.serverValidationErrors.includes('External url has already been taken')) {
return this.$options.i18n.validations.externalUrlTaken;
} else if (!this.externalUrl || !isSafeURL(this.externalUrl)) {
}
if (!this.externalUrl || !isSafeURL(this.externalUrl)) {
return this.$options.i18n.validations.invalidUrl;
}
......@@ -266,7 +271,7 @@ export default {
* - Single rule?
* - Multi rule?
*/
submit() {
async submit() {
let submission;
this.serverValidationErrors = [];
......@@ -275,16 +280,18 @@ export default {
const valid = this.isExternalApprovalRule ? this.isValidExternalApprovalRule : this.isValid;
if (!valid) {
submission = Promise.resolve();
submission = Promise.resolve;
} else if (this.isFallbackSubmission) {
submission = this.submitFallback();
submission = this.submitFallback;
} else if (!this.isMultiSubmission) {
submission = this.submitSingleRule();
submission = this.submitSingleRule;
} else {
submission = this.submitRule();
submission = this.submitRule;
}
submission.catch((failureResponse) => {
try {
await submission();
} catch (failureResponse) {
if (this.isExternalApprovalRule) {
this.serverValidationErrors = failureResponse?.response?.data?.message || [];
} else {
......@@ -292,9 +299,7 @@ export default {
failureResponse?.response?.data?.message || {},
);
}
});
return submission;
}
},
/**
* Submit the rule, by either put-ing or post-ing.
......@@ -304,10 +309,13 @@ export default {
const data = this.externalRuleSubmissionData;
return data.id ? this.putExternalApprovalRule(data) : this.postExternalApprovalRule(data);
}
const data = this.submissionData;
if (!this.settings.allowMultiRule && this.settings.prefix === 'mr-edit') {
return data.id ? this.putRule(data) : this.postRegularRule(data);
}
return data.id ? this.putRule(data) : this.postRule(data);
},
/**
......@@ -416,86 +424,92 @@ export default {
<template>
<form novalidate @submit.prevent.stop="submit">
<div v-if="showName" class="form-group gl-form-group">
<label class="col-form-label">{{ $options.i18n.form.nameLabel }}</label>
<input
<gl-form-group
v-if="showName"
:label="$options.i18n.form.nameLabel"
:description="$options.i18n.form.nameDescription"
:state="isValidName"
:invalid-feedback="invalidName"
data-testid="name-group"
>
<gl-form-input
v-model="name"
:class="{ 'is-invalid': !isValidName }"
:disabled="isNameDisabled"
class="gl-form-input form-control"
name="name"
type="text"
:state="isValidName"
data-qa-selector="rule_name_field"
data-testid="name"
/>
<span class="invalid-feedback">{{ isValidName ? '' : invalidName }}</span>
<small class="form-text text-gl-muted">
{{ $options.i18n.form.nameDescription }}
</small>
</div>
<div v-if="showProtectedBranch" class="form-group gl-form-group">
<label class="col-form-label">{{ $options.i18n.form.protectedBranchLabel }}</label>
</gl-form-group>
<gl-form-group
v-if="showProtectedBranch"
:label="$options.i18n.form.protectedBranchLabel"
:description="$options.i18n.form.protectedBranchDescription"
:state="isValidBranches"
:invalid-feedback="invalidBranches"
data-testid="branches-group"
>
<branches-select
v-model="branchesToAdd"
:project-id="settings.projectId"
:is-invalid="!isValidBranches"
:init-rule="rule"
/>
<span class="invalid-feedback">{{ isValidBranches ? '' : invalidBranches }}</span>
<small class="form-text text-gl-muted">
{{ $options.i18n.form.protectedBranchDescription }}
</small>
</div>
<div v-if="showApproverTypeSelect" class="form-group gl-form-group">
<label class="col-form-label">{{ $options.i18n.form.approvalTypeLabel }}</label>
</gl-form-group>
<gl-form-group v-if="showApproverTypeSelect" :label="$options.i18n.form.approvalTypeLabel">
<approver-type-select
v-model="ruleType"
:approver-type-options="$options.approverTypeOptions"
/>
</div>
<div v-if="!isExternalApprovalRule" class="form-group gl-form-group">
<label class="col-form-label">{{ $options.i18n.form.approvalsRequiredLabel }}</label>
<input
v-model.number="approvalsRequired"
:class="{ 'is-invalid': !isValidApprovalsRequired }"
class="gl-form-input form-control mw-6em"
name="approvals_required"
type="number"
:min="minApprovalsRequired"
data-qa-selector="approvals_required_field"
/>
<span class="invalid-feedback">{{
isValidApprovalsRequired ? '' : invalidApprovalsRequired
}}</span>
</div>
<div v-if="!isExternalApprovalRule" class="form-group gl-form-group">
<label class="col-form-label">{{ $options.i18n.form.approversLabel }}</label>
<approvers-select
v-model="approversToAdd"
:project-id="settings.projectId"
:skip-user-ids="userIds"
:skip-group-ids="groupIds"
:is-invalid="!isValidApprovers"
data-qa-selector="member_select_field"
/>
<span class="invalid-feedback">{{ isValidApprovers ? '' : invalidApprovers }}</span>
</div>
<div v-if="isExternalApprovalRule" class="form-group gl-form-group">
<label class="col-form-label">{{ approvalGateLabel }}</label>
<input
</gl-form-group>
<template v-if="!isExternalApprovalRule">
<gl-form-group
:label="$options.i18n.form.approvalsRequiredLabel"
:state="isValidApprovalsRequired"
:invalid-feedback="invalidApprovalsRequired"
data-testid="approvals-required-group"
>
<gl-form-input
v-model.number="approvalsRequired"
:state="isValidApprovalsRequired"
:min="minApprovalsRequired"
class="mw-6em"
type="number"
data-testid="approvals-required"
data-qa-selector="approvals_required_field"
/>
</gl-form-group>
<gl-form-group
:label="$options.i18n.form.approversLabel"
:state="isValidApprovers"
:invalid-feedback="invalidApprovers"
data-testid="approvers-group"
>
<approvers-select
v-model="approversToAdd"
:project-id="settings.projectId"
:skip-user-ids="userIds"
:skip-group-ids="groupIds"
:is-invalid="!isValidApprovers"
data-qa-selector="member_select_field"
/>
</gl-form-group>
</template>
<gl-form-group
v-if="isExternalApprovalRule"
:label="approvalGateLabel"
:description="$options.i18n.form.approvalGateDescription"
:state="isValidApprovalGateUrl"
:invalid-feedback="invalidApprovalGateUrl"
data-testid="approval-gate-url-group"
>
<gl-form-input
v-model="externalUrl"
:class="{ 'is-invalid': !isValidApprovalGateUrl }"
class="gl-form-input form-control"
name="approval_gate_url"
:state="isValidApprovalGateUrl"
type="url"
data-qa-selector="external_url_field"
data-testid="approval-gate-url"
/>
<span class="invalid-feedback">{{
isValidApprovalGateUrl ? '' : invalidApprovalGateUrl
}}</span>
<small class="form-text text-gl-muted">
{{ $options.i18n.form.approvalGateDescription }}
</small>
</div>
</gl-form-group>
<div v-if="!isExternalApprovalRule" class="bordered-box overflow-auto h-12em">
<approvers-list v-model="approvers" />
</div>
......
......@@ -62,7 +62,9 @@ RSpec.describe 'Merge request > User edits MR with approval rules', :js do
click_button "Add approval rule"
fill_in "name", with: rule_name
within_fieldset('Rule name') do
fill_in with: rule_name
end
add_approval_rule_member('user', approver.name)
......
......@@ -228,9 +228,11 @@ RSpec.describe 'Merge request > User sets approvers', :js do
find('.merge-request').click_on 'Edit'
open_modal
expect(page).to have_field 'approvals_required', exact: 2
within_fieldset('Approvals required') do
expect(find_field.value).to eq '2'
fill_in 'approvals_required', with: '3'
fill_in with: '3'
end
click_button 'Update approval rule'
click_on('Save changes')
......@@ -245,7 +247,9 @@ RSpec.describe 'Merge request > User sets approvers', :js do
open_modal
expect(page).to have_field 'approvals_required', exact: 3
within_fieldset('Approvals required') do
expect(find_field.value).to eq '3'
end
end
end
end
......
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ApproverTypeSelect from 'ee/approvals/components/approver_type_select.vue';
import ApproversList from 'ee/approvals/components/approvers_list.vue';
......@@ -14,6 +15,8 @@ import {
} from 'ee/approvals/constants';
import { createStoreOptions } from 'ee/approvals/stores';
import projectSettingsModule from 'ee/approvals/stores/modules/project_settings';
import { stubComponent } from 'helpers/stub_component';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createExternalRule } from '../mocks';
......@@ -25,7 +28,7 @@ const TEST_RULE = {
users: [{ id: 1 }, { id: 2 }, { id: 3 }],
groups: [{ id: 1 }, { id: 2 }],
};
const TEST_PROTECTED_BRANCHES = [{ id: 2 }];
const TEST_PROTECTED_BRANCHES = [{ id: 2 }, { id: 3 }, { id: 4 }];
const TEST_RULE_WITH_PROTECTED_BRANCHES = {
...TEST_RULE,
protectedBranches: TEST_PROTECTED_BRANCHES,
......@@ -67,36 +70,48 @@ describe('EE Approvals RuleForm', () => {
let store;
let actions;
const createComponent = (props = {}, options = {}) => {
wrapper = shallowMount(RuleForm, {
propsData: props,
store: new Vuex.Store(store),
provide: {
glFeatures: {
ffComplianceApprovalGates: true,
scopedApprovalRules: true,
...options.provide?.glFeatures,
const createComponent = (props = {}, features = {}) => {
wrapper = extendedWrapper(
shallowMount(RuleForm, {
propsData: props,
store: new Vuex.Store(store),
provide: {
glFeatures: {
ffComplianceApprovalGates: true,
scopedApprovalRules: true,
...features,
},
},
},
});
stubs: {
GlFormGroup: stubComponent(GlFormGroup, {
props: ['state', 'invalidFeedback'],
}),
GlFormInput: stubComponent(GlFormInput, {
props: ['state', 'disabled', 'value'],
template: `<input />`,
}),
},
}),
);
};
const findValidation = (node, hasProps = false) => ({
feedback: node.element.nextElementSibling.textContent,
isValid: hasProps ? !node.props('isInvalid') : !node.classes('is-invalid'),
});
const findNameInput = () => wrapper.find('input[name=name]');
const findNameValidation = () => findValidation(findNameInput(), false);
const findApprovalsRequiredInput = () => wrapper.find('input[name=approvals_required]');
const findApprovalsRequiredValidation = () => findValidation(findApprovalsRequiredInput(), false);
const findApproversSelect = () => wrapper.find(ApproversSelect);
const findApproversValidation = () => findValidation(findApproversSelect(), true);
const findApproversList = () => wrapper.find(ApproversList);
const findBranchesSelect = () => wrapper.find(BranchesSelect);
const findForm = () => wrapper.find('form');
const findNameInput = () => wrapper.findByTestId('name');
const findNameValidation = () => wrapper.findByTestId('name-group');
const findApprovalsRequiredInput = () => wrapper.findByTestId('approvals-required');
const findApprovalsRequiredValidation = () => wrapper.findByTestId('approvals-required-group');
const findApproversSelect = () => wrapper.findComponent(ApproversSelect);
const findApproversValidation = () => wrapper.findByTestId('approvers-group');
const findApproversList = () => wrapper.findComponent(ApproversList);
const findBranchesSelect = () => wrapper.findComponent(BranchesSelect);
const findApproverTypeSelect = () => wrapper.findComponent(ApproverTypeSelect);
const findExternalUrlInput = () => wrapper.find('input[name=approval_gate_url');
const findExternalUrlValidation = () => findValidation(findExternalUrlInput(), false);
const findBranchesValidation = () => findValidation(findBranchesSelect(), true);
const findExternalUrlInput = () => wrapper.findByTestId('approval-gate-url');
const findExternalUrlValidation = () => wrapper.findByTestId('approval-gate-url-group');
const findBranchesValidation = () => wrapper.findByTestId('branches-group');
const inputsAreValid = (inputs) => inputs.every((x) => x.props('state'));
const findValidations = () => [
findNameValidation(),
findApprovalsRequiredValidation(),
......@@ -147,48 +162,51 @@ describe('EE Approvals RuleForm', () => {
});
it('on load, it populates initial protected branch ids', () => {
expect(wrapper.vm.branches).toEqual(TEST_PROTECTED_BRANCHES.map((x) => x.id));
expect(findBranchesSelect().props('initRule').protectedBranches).toEqual(
TEST_PROTECTED_BRANCHES,
);
});
});
describe('without initRule', () => {
beforeEach(() => {
store.state.settings.protectedBranches = TEST_PROTECTED_BRANCHES;
createComponent({
isMrEdit: false,
});
});
it('at first, shows no validation', () => {
const inputs = findValidationsWithBranch();
const invalidInputs = inputs.filter((x) => !x.isValid);
const feedbacks = inputs.map((x) => x.feedback);
createComponent({
isMrEdit: false,
});
expect(invalidInputs.length).toBe(0);
expect(feedbacks.every((str) => !str.length)).toBe(true);
expect(inputsAreValid(findValidationsWithBranch())).toBe(true);
});
it('on submit, shows branches validation', (done) => {
wrapper.vm.branches = ['3'];
wrapper.vm.submit();
it('on submit, shows branches validation', async () => {
createComponent({
isMrEdit: false,
});
await findBranchesSelect().vm.$emit('input', '3');
await findForm().trigger('submit');
await nextTick();
Vue.nextTick()
.then(() => {
expect(findBranchesValidation()).toEqual({
isValid: false,
feedback: 'Please select a valid target branch',
});
})
.then(done)
.catch(done.fail);
const branchesGroup = findBranchesValidation();
expect(branchesGroup.props('state')).toBe(false);
expect(branchesGroup.props('invalidFeedback')).toBe(
'Please select a valid target branch',
);
});
it('on submit with data, posts rule', () => {
it('on submit with data, posts rule', async () => {
createComponent({
isMrEdit: false,
});
const users = [1, 2];
const groups = [2, 3];
const userRecords = users.map((id) => ({ id, type: TYPE_USER }));
const groupRecords = groups.map((id) => ({ id, type: TYPE_GROUP }));
const branches = TEST_PROTECTED_BRANCHES.map((x) => x.id);
const branches = [TEST_PROTECTED_BRANCHES[0].id];
const expected = {
id: null,
name: 'Lorem',
......@@ -201,12 +219,11 @@ describe('EE Approvals RuleForm', () => {
protectedBranchIds: branches,
};
findNameInput().setValue(expected.name);
findApprovalsRequiredInput().setValue(expected.approvalsRequired);
wrapper.vm.approvers = groupRecords.concat(userRecords);
wrapper.vm.branches = expected.protectedBranchIds;
wrapper.vm.submit();
await findNameInput().vm.$emit('input', expected.name);
await findApprovalsRequiredInput().vm.$emit('input', expected.approvalsRequired);
await findApproversList().vm.$emit('input', [...groupRecords, ...userRecords]);
await findBranchesSelect().vm.$emit('input', branches[0]);
await findForm().trigger('submit');
expect(actions.postRule).toHaveBeenCalledWith(expect.anything(), expected);
});
......@@ -227,7 +244,7 @@ describe('EE Approvals RuleForm', () => {
});
it('on load, it populates the external URL', () => {
expect(findExternalUrlInput().element.value).toBe(
expect(findExternalUrlInput().props('value')).toBe(
TEST_EXTERNAL_APPROVAL_RULE.externalUrl,
);
});
......@@ -258,35 +275,28 @@ describe('EE Approvals RuleForm', () => {
});
it('at first, shows no validation', () => {
const inputs = findValidationForExternal();
const invalidInputs = inputs.filter((x) => !x.isValid);
const feedbacks = inputs.map((x) => x.feedback);
expect(invalidInputs.length).toBe(0);
expect(feedbacks.every((str) => !str.length)).toBe(true);
expect(inputsAreValid(findValidationForExternal())).toBe(true);
});
it('on submit, does not dispatch action', () => {
wrapper.vm.submit();
it('on submit, does not dispatch action', async () => {
await findForm().trigger('submit');
expect(actions.postExternalApprovalRule).not.toHaveBeenCalled();
});
it('on submit, shows name validation', async () => {
findExternalUrlInput().setValue('');
wrapper.vm.submit();
it('on submit, shows external URL validation', async () => {
findNameInput().setValue('');
await Vue.nextTick();
await findForm().trigger('submit');
await nextTick();
expect(findExternalUrlValidation()).toEqual({
isValid: false,
feedback: 'Please provide a valid URL',
});
const externalUrlGroup = findExternalUrlValidation();
expect(externalUrlGroup.props('state')).toBe(false);
expect(externalUrlGroup.props('invalidFeedback')).toBe('Please provide a valid URL');
});
describe('with valid data', () => {
const branches = TEST_PROTECTED_BRANCHES.map((x) => x.id);
const branches = [TEST_PROTECTED_BRANCHES[0].id];
const expected = {
id: null,
name: 'Lorem',
......@@ -294,14 +304,14 @@ describe('EE Approvals RuleForm', () => {
protectedBranchIds: branches,
};
beforeEach(() => {
findNameInput().setValue(expected.name);
findExternalUrlInput().setValue(expected.externalUrl);
wrapper.vm.branches = expected.protectedBranchIds;
beforeEach(async () => {
await findNameInput().vm.$emit('input', expected.name);
await findExternalUrlInput().vm.$emit('input', expected.externalUrl);
await findBranchesSelect().vm.$emit('input', branches[0]);
});
it('on submit, posts external approval rule', () => {
wrapper.vm.submit();
it('on submit, posts external approval rule', async () => {
await findForm().trigger('submit');
expect(actions.postExternalApprovalRule).toHaveBeenCalledWith(
expect.anything(),
......@@ -311,16 +321,16 @@ describe('EE Approvals RuleForm', () => {
it('when submitted with a duplicate external URL, shows the "url already taken" validation', async () => {
store.state.settings.prefix = 'project-settings';
jest.spyOn(wrapper.vm, 'postExternalApprovalRule').mockRejectedValueOnce(urlTakenError);
wrapper.vm.submit();
actions.postExternalApprovalRule.mockRejectedValueOnce(urlTakenError);
await findForm().trigger('submit');
await waitForPromises();
expect(findExternalUrlValidation()).toEqual({
isValid: false,
feedback: 'External url has already been taken',
});
const externalUrlGroup = findExternalUrlValidation();
expect(externalUrlGroup.props('state')).toBe(false);
expect(externalUrlGroup.props('invalidFeedback')).toBe(
'External url has already been taken',
);
});
});
});
......@@ -328,69 +338,50 @@ describe('EE Approvals RuleForm', () => {
describe('without initRule', () => {
beforeEach(() => {
createComponent();
createComponent({ isMrEdit: false });
});
it('at first, shows no validation', () => {
const inputs = findValidations();
const invalidInputs = inputs.filter((x) => !x.isValid);
const feedbacks = inputs.map((x) => x.feedback);
expect(invalidInputs.length).toBe(0);
expect(feedbacks.every((str) => !str.length)).toBe(true);
expect(inputsAreValid(findValidationsWithBranch())).toBe(true);
});
it('on submit, does not dispatch action', () => {
wrapper.vm.submit();
it('on submit, does not dispatch action', async () => {
await findForm().trigger('submit');
expect(actions.postRule).not.toHaveBeenCalled();
});
it('on submit, shows name validation', (done) => {
it('on submit, shows name validation', async () => {
findNameInput().setValue('');
wrapper.vm.submit();
await findForm().trigger('submit');
await nextTick();
Vue.nextTick()
.then(() => {
expect(findNameValidation()).toEqual({
isValid: false,
feedback: 'Please provide a name',
});
})
.then(done)
.catch(done.fail);
const nameGroup = findNameValidation();
expect(nameGroup.props('state')).toBe(false);
expect(nameGroup.props('invalidFeedback')).toBe('Please provide a name');
});
it('on submit, shows approvalsRequired validation', (done) => {
findApprovalsRequiredInput().setValue(-1);
wrapper.vm.submit();
it('on submit, shows approvalsRequired validation', async () => {
await findApprovalsRequiredInput().vm.$emit('input', -1);
await findForm().trigger('submit');
await nextTick();
Vue.nextTick()
.then(() => {
expect(findApprovalsRequiredValidation()).toEqual({
isValid: false,
feedback: 'Please enter a non-negative number',
});
})
.then(done)
.catch(done.fail);
const approvalsRequiredGroup = findApprovalsRequiredValidation();
expect(approvalsRequiredGroup.props('state')).toBe(false);
expect(approvalsRequiredGroup.props('invalidFeedback')).toBe(
'Please enter a non-negative number',
);
});
it('on submit, shows approvers validation', (done) => {
wrapper.vm.approvers = [];
wrapper.vm.submit();
it('on submit, shows approvers validation', async () => {
await findApproversList().vm.$emit('input', []);
await findForm().trigger('submit');
await nextTick();
Vue.nextTick()
.then(() => {
expect(findApproversValidation()).toEqual({
isValid: false,
feedback: 'Please select and add a member',
});
})
.then(done)
.catch(done.fail);
const approversGroup = findApproversValidation();
expect(approversGroup.props('state')).toBe(false);
expect(approversGroup.props('invalidFeedback')).toBe('Please select and add a member');
});
describe('with valid data', () => {
......@@ -398,7 +389,7 @@ describe('EE Approvals RuleForm', () => {
const groups = [2, 3];
const userRecords = users.map((id) => ({ id, type: TYPE_USER }));
const groupRecords = groups.map((id) => ({ id, type: TYPE_GROUP }));
const branches = TEST_PROTECTED_BRANCHES.map((x) => x.id);
const branches = [TEST_PROTECTED_BRANCHES[0].id];
const expected = {
id: null,
name: 'Lorem',
......@@ -411,46 +402,44 @@ describe('EE Approvals RuleForm', () => {
protectedBranchIds: branches,
};
beforeEach(() => {
findNameInput().setValue(expected.name);
findApprovalsRequiredInput().setValue(expected.approvalsRequired);
wrapper.vm.approvers = groupRecords.concat(userRecords);
wrapper.vm.branches = expected.protectedBranchIds;
beforeEach(async () => {
await findNameInput().vm.$emit('input', expected.name);
await findApprovalsRequiredInput().vm.$emit('input', expected.approvalsRequired);
await findApproversList().vm.$emit('input', [...groupRecords, ...userRecords]);
await findBranchesSelect().vm.$emit('input', branches[0]);
});
it('on submit, posts rule', () => {
wrapper.vm.submit();
it('on submit, posts rule', async () => {
await findForm().trigger('submit');
expect(actions.postRule).toHaveBeenCalledWith(expect.anything(), expected);
});
it('when submitted with a duplicate name, shows the "taken name" validation', async () => {
store.state.settings.prefix = 'project-settings';
jest.spyOn(wrapper.vm, 'postRule').mockRejectedValueOnce(nameTakenError);
wrapper.vm.submit();
actions.postRule.mockRejectedValueOnce(nameTakenError);
await wrapper.vm.$nextTick();
await findForm().trigger('submit');
await nextTick();
// We have to wait for two ticks because the promise needs to resolve
// AND the result has to update into the UI
await wrapper.vm.$nextTick();
await nextTick();
expect(findNameValidation()).toEqual({
isValid: false,
feedback: 'Rule name is already taken.',
});
const nameGroup = findNameValidation();
expect(nameGroup.props('state')).toBe(false);
expect(nameGroup.props('invalidFeedback')).toBe('Rule name is already taken.');
});
});
it('adds selected approvers on selection', () => {
it('adds selected approvers on selection', async () => {
const orig = [{ id: 7, type: TYPE_GROUP }];
const selected = [{ id: 2, type: TYPE_USER }];
const expected = [...orig, ...selected];
wrapper.setData({ approvers: orig });
wrapper.vm.$options.watch.approversToAdd.call(wrapper.vm, selected);
await findApproversSelect().vm.$emit('input', orig);
await findApproversSelect().vm.$emit('input', selected);
expect(wrapper.vm.approvers).toEqual(expected);
expect(findApproversList().props('value')).toEqual(expected);
});
});
......@@ -458,6 +447,7 @@ describe('EE Approvals RuleForm', () => {
beforeEach(() => {
createComponent({
initRule: TEST_RULE,
isMrEdit: false,
});
});
......@@ -490,34 +480,22 @@ describe('EE Approvals RuleForm', () => {
protectedBranchIds: [],
};
beforeEach(() => {
findNameInput().setValue(expected.name);
findApprovalsRequiredInput().setValue(expected.approvalsRequired);
wrapper.vm.approvers = groupRecords.concat(userRecords);
wrapper.vm.branches = expected.protectedBranchIds;
});
it('on submit, puts rule', () => {
wrapper.vm.submit();
it('on submit, puts rule', async () => {
await findForm().trigger('submit');
expect(actions.putRule).toHaveBeenCalledWith(expect.anything(), expected);
});
it('when submitted with a duplicate name, shows the "taken name" validation', async () => {
store.state.settings.prefix = 'project-settings';
jest.spyOn(wrapper.vm, 'putRule').mockRejectedValueOnce(nameTakenError);
actions.putRule.mockRejectedValueOnce(nameTakenError);
wrapper.vm.submit();
await findForm().trigger('submit');
await waitForPromises();
await wrapper.vm.$nextTick();
// We have to wait for two ticks because the promise needs to resolve
// AND the result has to update into the UI
await wrapper.vm.$nextTick();
expect(findNameValidation()).toEqual({
isValid: false,
feedback: 'Rule name is already taken.',
});
const nameGroup = findNameValidation();
expect(nameGroup.props('state')).toBe(false);
expect(nameGroup.props('invalidFeedback')).toBe('Rule name is already taken.');
});
});
});
......@@ -528,15 +506,14 @@ describe('EE Approvals RuleForm', () => {
initRule: TEST_FALLBACK_RULE,
});
wrapper.vm.name = '';
wrapper.vm.approvers = [];
wrapper.vm.approvalsRequired = TEST_APPROVALS_REQUIRED;
findNameInput().vm.$emit('input', '');
findApprovalsRequiredInput().vm.$emit('input', TEST_APPROVALS_REQUIRED);
findApproversList().vm.$emit('input', []);
});
describe('with empty name and empty approvers', () => {
beforeEach((done) => {
wrapper.vm.submit();
Vue.nextTick(done);
beforeEach(() => {
findForm().trigger('submit');
});
it('does not post rule', () => {
......@@ -550,16 +527,14 @@ describe('EE Approvals RuleForm', () => {
});
it('does not show any validation errors', () => {
expect(findValidations().every((x) => x.isValid)).toBe(true);
expect(inputsAreValid(findValidations())).toBe(true);
});
});
describe('with name and empty approvers', () => {
beforeEach((done) => {
wrapper.vm.name = 'Lorem';
wrapper.vm.submit();
Vue.nextTick(done);
beforeEach(() => {
findNameInput().vm.$emit('input', 'Lorem');
findForm().trigger('submit');
});
it('does not put fallback rule', () => {
......@@ -567,16 +542,14 @@ describe('EE Approvals RuleForm', () => {
});
it('shows approvers validation error', () => {
expect(findApproversValidation().isValid).toBe(false);
expect(findApproversValidation().props('state')).toBe(false);
});
});
describe('with empty name and approvers', () => {
beforeEach((done) => {
wrapper.vm.approvers = TEST_APPROVERS;
wrapper.vm.submit();
Vue.nextTick(done);
beforeEach(() => {
findApproversList().vm.$emit('input', TEST_APPROVERS);
findForm().trigger('submit');
});
it('does not put fallback rule', () => {
......@@ -584,17 +557,15 @@ describe('EE Approvals RuleForm', () => {
});
it('shows name validation error', () => {
expect(findNameValidation().isValid).toBe(false);
expect(findNameValidation().props('state')).toBe(false);
});
});
describe('with name and approvers', () => {
beforeEach((done) => {
wrapper.vm.approvers = [{ id: 7, type: TYPE_USER }];
wrapper.vm.name = 'Lorem';
wrapper.vm.submit();
Vue.nextTick(done);
beforeEach(() => {
findApproversList().vm.$emit('input', [{ id: 7, type: TYPE_USER }]);
findNameInput().vm.$emit('input', 'Lorem');
findForm().trigger('submit');
});
it('does not put fallback rule', () => {
......@@ -627,8 +598,8 @@ describe('EE Approvals RuleForm', () => {
]);
});
it('on submit, does not remove hidden groups', () => {
wrapper.vm.submit();
it('on submit, does not remove hidden groups', async () => {
await findForm().trigger('submit');
expect(actions.putRule).toHaveBeenCalledWith(
expect.anything(),
......@@ -640,11 +611,16 @@ describe('EE Approvals RuleForm', () => {
describe('and hidden groups removed', () => {
beforeEach(() => {
wrapper.vm.approvers = wrapper.vm.approvers.filter((x) => x.type !== TYPE_HIDDEN_GROUPS);
findApproversList().vm.$emit(
'input',
findApproversList()
.props('value')
.filter((x) => x.type !== TYPE_HIDDEN_GROUPS),
);
});
it('on submit, removes hidden groups', () => {
wrapper.vm.submit();
it('on submit, removes hidden groups', async () => {
await findForm().trigger('submit');
expect(actions.putRule).toHaveBeenCalledWith(
expect.anything(),
......@@ -679,9 +655,9 @@ describe('EE Approvals RuleForm', () => {
describe('with approval suggestions', () => {
describe.each`
defaultRuleName | expectedDisabledAttribute | approverTypeSelect
${'Vulnerability-Check'} | ${'disabled'} | ${false}
${'License-Check'} | ${'disabled'} | ${false}
${'Foo Bar Baz'} | ${undefined} | ${true}
${'Vulnerability-Check'} | ${true} | ${false}
${'License-Check'} | ${true} | ${false}
${'Foo Bar Baz'} | ${false} | ${true}
`(
'with defaultRuleName set to $defaultRuleName',
({ defaultRuleName, expectedDisabledAttribute, approverTypeSelect }) => {
......@@ -696,7 +672,7 @@ describe('EE Approvals RuleForm', () => {
it(`it ${
expectedDisabledAttribute ? 'disables' : 'does not disable'
} the name text field`, () => {
expect(findNameInput().attributes('disabled')).toBe(expectedDisabledAttribute);
expect(findNameInput().props('disabled')).toBe(expectedDisabledAttribute);
});
it(`${
......@@ -716,7 +692,7 @@ describe('EE Approvals RuleForm', () => {
});
it('does not disable the name text field', () => {
expect(findNameInput().attributes('disabled')).toBe(undefined);
expect(findNameInput().props('disabled')).toBe(false);
});
});
......@@ -728,7 +704,7 @@ describe('EE Approvals RuleForm', () => {
});
it('does not disable the name text field', () => {
expect(findNameInput().attributes('disabled')).toBe(undefined);
expect(findNameInput().props('disabled')).toBe(false);
});
});
......@@ -740,7 +716,7 @@ describe('EE Approvals RuleForm', () => {
});
it('disables the name text field', () => {
expect(findNameInput().attributes('disabled')).toBe('disabled');
expect(findNameInput().props('disabled')).toBe(true);
});
});
......@@ -752,7 +728,7 @@ describe('EE Approvals RuleForm', () => {
});
it('disables the name text field', () => {
expect(findNameInput().attributes('disabled')).toBe('disabled');
expect(findNameInput().props('disabled')).toBe(true);
});
});
});
......@@ -781,15 +757,13 @@ describe('EE Approvals RuleForm', () => {
beforeEach(() => {
store.state.settings.lockedApprovalsRuleName = lockedRuleName;
createComponent();
wrapper.vm.approvalsRequired = TEST_APPROVALS_REQUIRED;
findApprovalsRequiredInput().vm.$emit('input', TEST_APPROVALS_REQUIRED);
});
describe('with approvers selected', () => {
beforeEach(() => {
wrapper.vm.approvers = TEST_APPROVERS;
wrapper.vm.submit();
return Vue.nextTick();
findApproversList().vm.$emit('input', TEST_APPROVERS);
findForm().trigger('submit');
});
it('posts new rule', () => {
......@@ -806,9 +780,7 @@ describe('EE Approvals RuleForm', () => {
describe('without approvers', () => {
beforeEach(() => {
wrapper.vm.submit();
return Vue.nextTick();
findForm().trigger('submit');
});
it('puts fallback rule', () => {
......@@ -826,20 +798,17 @@ describe('EE Approvals RuleForm', () => {
`('with init rule', ({ lockedRuleName, inputName, expectedNameSubmitted }) => {
beforeEach(() => {
store.state.settings.lockedApprovalsRuleName = lockedRuleName;
createComponent({
initRule: TEST_RULE,
});
wrapper.vm.approvalsRequired = TEST_APPROVALS_REQUIRED;
});
describe('with empty name and empty approvers', () => {
beforeEach(() => {
wrapper.vm.name = '';
wrapper.vm.approvers = [];
wrapper.vm.submit();
createComponent({
initRule: { ...TEST_RULE, name: '' },
});
findApprovalsRequiredInput().vm.$emit('input', TEST_APPROVALS_REQUIRED);
findApproversList().vm.$emit('input', []);
return Vue.nextTick();
findForm().trigger('submit');
});
it('deletes rule', () => {
......@@ -854,12 +823,14 @@ describe('EE Approvals RuleForm', () => {
});
describe('with name and approvers', () => {
beforeEach((done) => {
wrapper.vm.name = inputName;
wrapper.vm.approvers = TEST_APPROVERS;
wrapper.vm.submit();
beforeEach(() => {
createComponent({
initRule: { ...TEST_RULE, name: inputName },
});
findApprovalsRequiredInput().vm.$emit('input', TEST_APPROVALS_REQUIRED);
findApproversList().vm.$emit('input', TEST_APPROVERS);
Vue.nextTick(done);
findForm().trigger('submit');
});
it('puts rule', () => {
......@@ -879,18 +850,9 @@ describe('EE Approvals RuleForm', () => {
describe('when the approval gates feature is disabled', () => {
it('does not render the approver type select input', async () => {
createComponent(
{ isMrEdit: false },
{
provide: {
glFeatures: {
ffComplianceApprovalGates: false,
},
},
},
);
createComponent({ isMrEdit: false }, { ffComplianceApprovalGates: false });
await Vue.nextTick();
await nextTick();
expect(findApproverTypeSelect().exists()).toBe(false);
});
......
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