gitlab-org--gitlab-foss/spec/frontend/crm/form_spec.js

339 lines
11 KiB
JavaScript

import { GlAlert, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormGroup } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Form from '~/crm/components/form.vue';
import routes from '~/crm/contacts/routes';
import createContactMutation from '~/crm/contacts/components/graphql/create_contact.mutation.graphql';
import updateContactMutation from '~/crm/contacts/components/graphql/update_contact.mutation.graphql';
import getGroupContactsQuery from '~/crm/contacts/components/graphql/get_group_contacts.query.graphql';
import createOrganizationMutation from '~/crm/organizations/components/graphql/create_organization.mutation.graphql';
import getGroupOrganizationsQuery from '~/crm/organizations/components/graphql/get_group_organizations.query.graphql';
import {
createContactMutationErrorResponse,
createContactMutationResponse,
getGroupContactsQueryResponse,
updateContactMutationErrorResponse,
updateContactMutationResponse,
createOrganizationMutationErrorResponse,
createOrganizationMutationResponse,
getGroupOrganizationsQueryResponse,
} from './mock_data';
const FORM_CREATE_CONTACT = 'create contact';
const FORM_UPDATE_CONTACT = 'update contact';
const FORM_CREATE_ORG = 'create organization';
describe('Reusable form component', () => {
Vue.use(VueApollo);
Vue.use(VueRouter);
const DEFAULT_RESPONSES = {
createContact: Promise.resolve(createContactMutationResponse),
updateContact: Promise.resolve(updateContactMutationResponse),
createOrg: Promise.resolve(createOrganizationMutationResponse),
};
let wrapper;
let handler;
let fakeApollo;
let router;
beforeEach(() => {
router = new VueRouter({
base: '',
mode: 'history',
routes,
});
router.push('/test');
handler = jest.fn().mockImplementation((key) => DEFAULT_RESPONSES[key]);
const hanlderWithKey = (key) => (...args) => handler(key, ...args);
fakeApollo = createMockApollo([
[createContactMutation, hanlderWithKey('createContact')],
[updateContactMutation, hanlderWithKey('updateContact')],
[createOrganizationMutation, hanlderWithKey('createOrg')],
]);
fakeApollo.clients.defaultClient.cache.writeQuery({
query: getGroupContactsQuery,
variables: { groupFullPath: 'flightjs' },
data: getGroupContactsQueryResponse.data,
});
fakeApollo.clients.defaultClient.cache.writeQuery({
query: getGroupOrganizationsQuery,
variables: { groupFullPath: 'flightjs' },
data: getGroupOrganizationsQueryResponse.data,
});
});
const mockToastShow = jest.fn();
const findSaveButton = () => wrapper.findByTestId('save-button');
const findForm = () => wrapper.find('form');
const findError = () => wrapper.findComponent(GlAlert);
const findFormGroup = (at) => wrapper.findAllComponents(GlFormGroup).at(at);
const mountComponent = (propsData) => {
wrapper = shallowMountExtended(Form, {
router,
apolloProvider: fakeApollo,
propsData: { drawerOpen: true, ...propsData },
mocks: {
$toast: {
show: mockToastShow,
},
},
});
};
const mountContact = ({ propsData, extraFields = [] } = {}) => {
mountComponent({
fields: [
{ name: 'firstName', label: 'First name', required: true },
{ name: 'lastName', label: 'Last name', required: true },
{ name: 'email', label: 'Email', required: true },
{ name: 'phone', label: 'Phone' },
{ name: 'description', label: 'Description' },
{
name: 'organizationId',
label: 'Organization',
values: [
{ key: 'gid://gitlab/CustomerRelations::Organization/1', value: 'GitLab' },
{ key: 'gid://gitlab/CustomerRelations::Organization/2', value: 'ABC Corp' },
],
},
...extraFields,
],
getQuery: {
query: getGroupContactsQuery,
variables: { groupFullPath: 'flightjs' },
},
getQueryNodePath: 'group.contacts',
...propsData,
});
};
const mountContactCreate = () => {
const propsData = {
title: 'New contact',
successMessage: 'Contact has been added.',
buttonLabel: 'Create contact',
mutation: createContactMutation,
additionalCreateParams: { groupId: 'gid://gitlab/Group/26' },
};
mountContact({ propsData });
};
const mountContactUpdate = () => {
const propsData = {
title: 'Edit contact',
successMessage: 'Contact has been updated.',
mutation: updateContactMutation,
existingId: 'gid://gitlab/CustomerRelations::Contact/12',
};
const extraFields = [{ name: 'active', label: 'Active', required: true, bool: true }];
mountContact({ propsData, extraFields });
};
const mountOrganization = ({ propsData } = {}) => {
mountComponent({
fields: [
{ name: 'name', label: 'Name', required: true },
{ name: 'defaultRate', label: 'Default rate', input: { type: 'number', step: '0.01' } },
{ name: 'description', label: 'Description' },
],
getQuery: {
query: getGroupOrganizationsQuery,
variables: { groupFullPath: 'flightjs' },
},
getQueryNodePath: 'group.organizations',
...propsData,
});
};
const mountOrganizationCreate = () => {
const propsData = {
title: 'New organization',
successMessage: 'Organization has been added.',
buttonLabel: 'Create organization',
mutation: createOrganizationMutation,
additionalCreateParams: { groupId: 'gid://gitlab/Group/26' },
};
mountOrganization({ propsData });
};
const forms = {
[FORM_CREATE_CONTACT]: {
mountFunction: mountContactCreate,
mutationErrorResponse: createContactMutationErrorResponse,
toastMessage: 'Contact has been added.',
},
[FORM_UPDATE_CONTACT]: {
mountFunction: mountContactUpdate,
mutationErrorResponse: updateContactMutationErrorResponse,
toastMessage: 'Contact has been updated.',
},
[FORM_CREATE_ORG]: {
mountFunction: mountOrganizationCreate,
mutationErrorResponse: createOrganizationMutationErrorResponse,
toastMessage: 'Organization has been added.',
},
};
const asTestParams = (...keys) => keys.map((name) => [name, forms[name]]);
afterEach(() => {
wrapper.destroy();
});
describe.each(asTestParams(FORM_CREATE_CONTACT, FORM_UPDATE_CONTACT))(
'%s form save button',
(name, { mountFunction }) => {
beforeEach(() => {
mountFunction();
});
it('should be disabled when required fields are empty', async () => {
wrapper.find('#firstName').vm.$emit('input', '');
await waitForPromises();
expect(findSaveButton().props('disabled')).toBe(true);
});
it('should not be disabled when required fields have values', async () => {
wrapper.find('#firstName').vm.$emit('input', 'A');
wrapper.find('#lastName').vm.$emit('input', 'B');
wrapper.find('#email').vm.$emit('input', 'C');
await waitForPromises();
expect(findSaveButton().props('disabled')).toBe(false);
});
},
);
describe.each(asTestParams(FORM_CREATE_ORG))('%s form save button', (name, { mountFunction }) => {
beforeEach(() => {
mountFunction();
});
it('should be disabled when required field is empty', async () => {
wrapper.find('#name').vm.$emit('input', '');
await waitForPromises();
expect(findSaveButton().props('disabled')).toBe(true);
});
it('should not be disabled when required field has a value', async () => {
wrapper.find('#name').vm.$emit('input', 'A');
await waitForPromises();
expect(findSaveButton().props('disabled')).toBe(false);
});
});
describe.each(asTestParams(FORM_CREATE_CONTACT, FORM_UPDATE_CONTACT, FORM_CREATE_ORG))(
'when %s mutation is successful',
(name, { mountFunction, toastMessage }) => {
it('form should display correct toast message', async () => {
mountFunction();
findForm().trigger('submit');
await waitForPromises();
expect(mockToastShow).toHaveBeenCalledWith(toastMessage);
});
},
);
describe.each(asTestParams(FORM_CREATE_CONTACT, FORM_UPDATE_CONTACT, FORM_CREATE_ORG))(
'when %s mutation fails',
(formName, { mutationErrorResponse, mountFunction }) => {
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation();
});
it('should show error on reject', async () => {
handler.mockRejectedValue('ERROR');
mountFunction();
findForm().trigger('submit');
await waitForPromises();
expect(findError().text()).toBe('Something went wrong. Please try again.');
});
it('should show error on error response', async () => {
handler.mockResolvedValue(mutationErrorResponse);
mountFunction();
findForm().trigger('submit');
await waitForPromises();
expect(findError().text()).toBe(`${formName} is invalid.`);
});
},
);
describe('edit form', () => {
beforeEach(() => {
mountContactUpdate();
});
it.each`
index | id | component | value
${0} | ${'firstName'} | ${GlFormInput} | ${'Marty'}
${1} | ${'lastName'} | ${GlFormInput} | ${'McFly'}
${2} | ${'email'} | ${GlFormInput} | ${'example@gitlab.com'}
${4} | ${'description'} | ${GlFormInput} | ${undefined}
${3} | ${'phone'} | ${GlFormInput} | ${undefined}
${5} | ${'organizationId'} | ${GlFormSelect} | ${'gid://gitlab/CustomerRelations::Organization/2'}
`(
'should render the correct component for #$id with the value "$value"',
({ index, id, component, value }) => {
const findFormElement = () => findFormGroup(index).findComponent(component);
expect(findFormElement().attributes('id')).toBe(id);
expect(findFormElement().attributes('value')).toBe(value);
},
);
it('should render a checked GlFormCheckbox for #active', () => {
const activeCheckboxIndex = 6;
const findFormElement = () =>
findFormGroup(activeCheckboxIndex).findComponent(GlFormCheckbox);
expect(findFormElement().attributes('id')).toBe('active');
expect(findFormElement().attributes('checked')).toBe('true');
});
it('should include updated values in update mutation', () => {
wrapper.find('#firstName').vm.$emit('input', 'Michael');
wrapper
.find('#organizationId')
.vm.$emit('input', 'gid://gitlab/CustomerRelations::Organization/1');
findForm().trigger('submit');
expect(handler).toHaveBeenCalledWith('updateContact', {
input: {
active: true,
description: null,
email: 'example@gitlab.com',
firstName: 'Michael',
id: 'gid://gitlab/CustomerRelations::Contact/12',
lastName: 'McFly',
organizationId: 'gid://gitlab/CustomerRelations::Organization/1',
phone: null,
},
});
});
});
});