Commit 32d6f322 authored by Tom Quirk's avatar Tom Quirk

Improvements to integration_seetings_form_spec

- Move toast/error messages to constants file
- limit beforeEach for readability
- test event handling rather than unit-testing class methods
- add missing coverage for some event types
parent cbf57cea
import { s__ } from '~/locale'; import { s__, __ } from '~/locale';
export const TEST_INTEGRATION_EVENT = 'testIntegration'; export const TEST_INTEGRATION_EVENT = 'testIntegration';
export const SAVE_INTEGRATION_EVENT = 'saveIntegration'; export const SAVE_INTEGRATION_EVENT = 'saveIntegration';
...@@ -21,3 +21,9 @@ export const overrideDropdownDescriptions = { ...@@ -21,3 +21,9 @@ export const overrideDropdownDescriptions = {
'Integrations|Default settings are inherited from the instance level.', 'Integrations|Default settings are inherited from the instance level.',
), ),
}; };
export const I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE = s__(
'Integrations|Connection failed. Please check your settings.',
);
export const I18N_DEFAULT_ERROR_MESSAGE = __('Something went wrong on our end.');
export const I18N_SUCCESSFUL_CONNECTION_MESSAGE = s__('Integrations|Connection successful.');
import { delay } from 'lodash'; import { delay } from 'lodash';
import { __, s__ } from '~/locale';
import toast from '~/vue_shared/plugins/global_toast'; import toast from '~/vue_shared/plugins/global_toast';
import axios from '../lib/utils/axios_utils'; import axios from '../lib/utils/axios_utils';
import initForm from './edit'; import initForm from './edit';
...@@ -10,6 +9,9 @@ import { ...@@ -10,6 +9,9 @@ import {
GET_JIRA_ISSUE_TYPES_EVENT, GET_JIRA_ISSUE_TYPES_EVENT,
TOGGLE_INTEGRATION_EVENT, TOGGLE_INTEGRATION_EVENT,
VALIDATE_INTEGRATION_FORM_EVENT, VALIDATE_INTEGRATION_FORM_EVENT,
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
} from './constants'; } from './constants';
export default class IntegrationSettingsForm { export default class IntegrationSettingsForm {
...@@ -104,11 +106,7 @@ export default class IntegrationSettingsForm { ...@@ -104,11 +106,7 @@ export default class IntegrationSettingsForm {
return this.fetchTestSettings(formData) return this.fetchTestSettings(formData)
.then( .then(
({ ({
data: { data: { issuetypes, error, message = I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE },
issuetypes,
error,
message = s__('Integrations|Connection failed. Please check your settings.'),
},
}) => { }) => {
if (error || !issuetypes?.length) { if (error || !issuetypes?.length) {
eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT); eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
...@@ -118,7 +116,7 @@ export default class IntegrationSettingsForm { ...@@ -118,7 +116,7 @@ export default class IntegrationSettingsForm {
dispatch('receiveJiraIssueTypesSuccess', issuetypes); dispatch('receiveJiraIssueTypesSuccess', issuetypes);
}, },
) )
.catch(({ message = __('Something went wrong on our end.') }) => { .catch(({ message = I18N_DEFAULT_ERROR_MESSAGE }) => {
dispatch('receiveJiraIssueTypesError', message); dispatch('receiveJiraIssueTypesError', message);
}); });
} }
...@@ -140,11 +138,11 @@ export default class IntegrationSettingsForm { ...@@ -140,11 +138,11 @@ export default class IntegrationSettingsForm {
toast(`${data.message} ${data.service_response}`); toast(`${data.message} ${data.service_response}`);
} else { } else {
this.vue.$store.dispatch('receiveJiraIssueTypesSuccess', data.issuetypes); this.vue.$store.dispatch('receiveJiraIssueTypesSuccess', data.issuetypes);
toast(s__('Integrations|Connection successful.')); toast(I18N_SUCCESSFUL_CONNECTION_MESSAGE);
} }
}) })
.catch(() => { .catch(() => {
toast(__('Something went wrong on our end.')); toast(I18N_DEFAULT_ERROR_MESSAGE);
}) })
.finally(() => { .finally(() => {
this.vue.$store.dispatch('setIsTesting', false); this.vue.$store.dispatch('setIsTesting', false);
......
import MockAdaptor from 'axios-mock-adapter'; import MockAdaptor from 'axios-mock-adapter';
import IntegrationSettingsForm from '~/integrations/integration_settings_form'; import IntegrationSettingsForm from '~/integrations/integration_settings_form';
import eventHub from '~/integrations/edit/event_hub';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import toast from '~/vue_shared/plugins/global_toast'; import toast from '~/vue_shared/plugins/global_toast';
import {
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
GET_JIRA_ISSUE_TYPES_EVENT,
TOGGLE_INTEGRATION_EVENT,
TEST_INTEGRATION_EVENT,
SAVE_INTEGRATION_EVENT,
} from '~/integrations/constants';
import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/vue_shared/plugins/global_toast'); jest.mock('~/vue_shared/plugins/global_toast');
jest.mock('lodash/delay', () => (callback) => callback());
const FIXTURE = 'services/edit_service.html';
describe('IntegrationSettingsForm', () => { describe('IntegrationSettingsForm', () => {
const FIXTURE = 'services/edit_service.html'; let integrationSettingsForm;
const mockVuexDispatch = () => {
const dispatchSpy = jest.fn();
integrationSettingsForm.vue.$store = { dispatch: dispatchSpy };
return dispatchSpy;
};
beforeEach(() => { beforeEach(() => {
loadFixtures(FIXTURE); loadFixtures(FIXTURE);
});
describe('constructor', () => {
let integrationSettingsForm;
beforeEach(() => {
integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
jest.spyOn(integrationSettingsForm, 'init').mockImplementation(() => {}); integrationSettingsForm.init();
}); });
describe('constructor', () => {
it('should initialize form element refs on class object', () => { it('should initialize form element refs on class object', () => {
// Form Reference
expect(integrationSettingsForm.$form).toBeDefined(); expect(integrationSettingsForm.$form).toBeDefined();
expect(integrationSettingsForm.$form.nodeName).toBe('FORM'); expect(integrationSettingsForm.$form.nodeName).toBe('FORM');
expect(integrationSettingsForm.formActive).toBeDefined(); expect(integrationSettingsForm.formActive).toBeDefined();
...@@ -32,174 +47,164 @@ describe('IntegrationSettingsForm', () => { ...@@ -32,174 +47,164 @@ describe('IntegrationSettingsForm', () => {
}); });
}); });
describe('toggleServiceState', () => { describe('event handling', () => {
let integrationSettingsForm; let mockAxios;
beforeEach(() => { beforeEach(() => {
integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); mockAxios = new MockAdaptor(axios);
jest.spyOn(axios, 'put');
}); });
afterEach(() => {
mockAxios.restore();
eventHub.dispose(); // clear event hub handlers
});
describe('when event hub receives `TOGGLE_INTEGRATION_EVENT`', () => {
it('should remove `novalidate` attribute to form when called with `true`', () => { it('should remove `novalidate` attribute to form when called with `true`', () => {
integrationSettingsForm.formActive = true; eventHub.$emit(TOGGLE_INTEGRATION_EVENT, true);
integrationSettingsForm.toggleServiceState();
expect(integrationSettingsForm.$form.getAttribute('novalidate')).toBe(null); expect(integrationSettingsForm.$form.getAttribute('novalidate')).toBe(null);
}); });
it('should set `novalidate` attribute to form when called with `false`', () => { it('should set `novalidate` attribute to form when called with `false`', () => {
integrationSettingsForm.formActive = false; eventHub.$emit(TOGGLE_INTEGRATION_EVENT, false);
integrationSettingsForm.toggleServiceState();
expect(integrationSettingsForm.$form.getAttribute('novalidate')).toBeDefined(); expect(integrationSettingsForm.$form.getAttribute('novalidate')).toBe('novalidate');
}); });
}); });
describe('testSettings', () => { describe('when event hub receives `TEST_INTEGRATION_EVENT`', () => {
let integrationSettingsForm; describe('when form is valid', () => {
let formData;
let mock;
beforeEach(() => { beforeEach(() => {
mock = new MockAdaptor(axios); jest.spyOn(integrationSettingsForm.$form, 'checkValidity').mockReturnValue(true);
jest.spyOn(axios, 'put');
integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
integrationSettingsForm.init();
formData = new FormData(integrationSettingsForm.$form);
});
afterEach(() => {
mock.restore();
}); });
it('should make an ajax request with provided `formData`', async () => { it('should make an ajax request with provided `formData`', async () => {
await integrationSettingsForm.testSettings(formData); eventHub.$emit(TEST_INTEGRATION_EVENT);
await waitForPromises();
expect(axios.put).toHaveBeenCalledWith(integrationSettingsForm.testEndPoint, formData); expect(axios.put).toHaveBeenCalledWith(
integrationSettingsForm.testEndPoint,
new FormData(integrationSettingsForm.$form),
);
}); });
it('should show success message if test is successful', async () => { it('should show success message if test is successful', async () => {
jest.spyOn(integrationSettingsForm.$form, 'submit').mockImplementation(() => {}); jest.spyOn(integrationSettingsForm.$form, 'submit').mockImplementation(() => {});
mock.onPut(integrationSettingsForm.testEndPoint).reply(200, { mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, {
error: false, error: false,
}); });
await integrationSettingsForm.testSettings(formData); eventHub.$emit(TEST_INTEGRATION_EVENT);
await waitForPromises();
expect(toast).toHaveBeenCalledWith('Connection successful.'); expect(toast).toHaveBeenCalledWith(I18N_SUCCESSFUL_CONNECTION_MESSAGE);
}); });
it('should show error message if ajax request responds with test error', async () => { it('should show error message if ajax request responds with test error', async () => {
const errorMessage = 'Test failed.'; const errorMessage = 'Test failed.';
const serviceResponse = 'some error'; const serviceResponse = 'some error';
mock.onPut(integrationSettingsForm.testEndPoint).reply(200, { mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, {
error: true, error: true,
message: errorMessage, message: errorMessage,
service_response: serviceResponse, service_response: serviceResponse,
test_failed: false, test_failed: false,
}); });
await integrationSettingsForm.testSettings(formData); eventHub.$emit(TEST_INTEGRATION_EVENT);
await waitForPromises();
expect(toast).toHaveBeenCalledWith(`${errorMessage} ${serviceResponse}`); expect(toast).toHaveBeenCalledWith(`${errorMessage} ${serviceResponse}`);
}); });
it('should show error message if ajax request failed', async () => { it('should show error message if ajax request failed', async () => {
const errorMessage = 'Something went wrong on our end.'; mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError();
mock.onPut(integrationSettingsForm.testEndPoint).networkError();
await integrationSettingsForm.testSettings(formData); eventHub.$emit(TEST_INTEGRATION_EVENT);
await waitForPromises();
expect(toast).toHaveBeenCalledWith(errorMessage); expect(toast).toHaveBeenCalledWith(I18N_DEFAULT_ERROR_MESSAGE);
}); });
it('should always dispatch `setIsTesting` with `false` once request is completed', async () => { it('should always dispatch `setIsTesting` with `false` once request is completed', async () => {
const dispatchSpy = jest.fn(); const dispatchSpy = mockVuexDispatch();
mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError();
mock.onPut(integrationSettingsForm.testEndPoint).networkError(); eventHub.$emit(TEST_INTEGRATION_EVENT);
await waitForPromises();
integrationSettingsForm.vue.$store = { dispatch: dispatchSpy };
await integrationSettingsForm.testSettings(formData);
expect(dispatchSpy).toHaveBeenCalledWith('setIsTesting', false); expect(dispatchSpy).toHaveBeenCalledWith('setIsTesting', false);
}); });
}); });
describe('getJiraIssueTypes', () => { describe('when form is invalid', () => {
let integrationSettingsForm;
let formData;
let mock;
beforeEach(() => { beforeEach(() => {
mock = new MockAdaptor(axios); jest.spyOn(integrationSettingsForm.$form, 'checkValidity').mockReturnValue(false);
jest.spyOn(integrationSettingsForm, 'testSettings');
});
jest.spyOn(axios, 'put'); it('should dispatch `setIsTesting` with `false` and not call `testSettings`', async () => {
const dispatchSpy = mockVuexDispatch();
integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); eventHub.$emit(TEST_INTEGRATION_EVENT);
integrationSettingsForm.init(); await waitForPromises();
formData = new FormData(integrationSettingsForm.$form); expect(dispatchSpy).toHaveBeenCalledWith('setIsTesting', false);
expect(integrationSettingsForm.testSettings).not.toHaveBeenCalled();
});
}); });
afterEach(() => {
mock.restore();
}); });
it('should always dispatch `requestJiraIssueTypes`', async () => { describe('when event hub receives `GET_JIRA_ISSUE_TYPES_EVENT`', () => {
const dispatchSpy = jest.fn(); it('should always dispatch `requestJiraIssueTypes`', () => {
const dispatchSpy = mockVuexDispatch();
mock.onPut(integrationSettingsForm.testEndPoint).networkError(); mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError();
integrationSettingsForm.vue.$store = { dispatch: dispatchSpy }; eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
await integrationSettingsForm.getJiraIssueTypes();
expect(dispatchSpy).toHaveBeenCalledWith('requestJiraIssueTypes'); expect(dispatchSpy).toHaveBeenCalledWith('requestJiraIssueTypes');
}); });
it('should make an ajax request with provided `formData`', async () => { it('should make an ajax request with provided `formData`', () => {
await integrationSettingsForm.getJiraIssueTypes(formData); eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
expect(axios.put).toHaveBeenCalledWith(integrationSettingsForm.testEndPoint, formData); expect(axios.put).toHaveBeenCalledWith(
integrationSettingsForm.testEndPoint,
new FormData(integrationSettingsForm.$form),
);
}); });
it('should dispatch `receiveJiraIssueTypesSuccess` with the correct payload if ajax request is successful', async () => { it('should dispatch `receiveJiraIssueTypesSuccess` with the correct payload if ajax request is successful', async () => {
const dispatchSpy = mockVuexDispatch();
const mockData = ['ISSUE', 'EPIC']; const mockData = ['ISSUE', 'EPIC'];
const dispatchSpy = jest.fn(); mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, {
mock.onPut(integrationSettingsForm.testEndPoint).reply(200, {
error: false, error: false,
issuetypes: mockData, issuetypes: mockData,
}); });
integrationSettingsForm.vue.$store = { dispatch: dispatchSpy }; eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
await waitForPromises();
await integrationSettingsForm.getJiraIssueTypes(formData);
expect(dispatchSpy).toHaveBeenCalledWith('receiveJiraIssueTypesSuccess', mockData); expect(dispatchSpy).toHaveBeenCalledWith('receiveJiraIssueTypesSuccess', mockData);
}); });
it.each(['something went wrong', undefined])( it.each(['Custom error message here', undefined])(
'should dispatch "receiveJiraIssueTypesError" with a message if the backend responds with error', 'should dispatch "receiveJiraIssueTypesError" with a message if the backend responds with error',
async (responseErrorMessage) => { async (responseErrorMessage) => {
const defaultErrorMessage = 'Connection failed. Please check your settings.'; const dispatchSpy = mockVuexDispatch();
const expectedErrorMessage = responseErrorMessage || defaultErrorMessage;
const dispatchSpy = jest.fn();
mock.onPut(integrationSettingsForm.testEndPoint).reply(200, { const expectedErrorMessage =
responseErrorMessage || I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE;
mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, {
error: true, error: true,
message: responseErrorMessage, message: responseErrorMessage,
}); });
integrationSettingsForm.vue.$store = { dispatch: dispatchSpy }; eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
await waitForPromises();
await integrationSettingsForm.getJiraIssueTypes(formData);
expect(dispatchSpy).toHaveBeenCalledWith( expect(dispatchSpy).toHaveBeenCalledWith(
'receiveJiraIssueTypesError', 'receiveJiraIssueTypesError',
...@@ -208,4 +213,40 @@ describe('IntegrationSettingsForm', () => { ...@@ -208,4 +213,40 @@ describe('IntegrationSettingsForm', () => {
}, },
); );
}); });
describe('when event hub receives `SAVE_INTEGRATION_EVENT`', () => {
describe('when form is valid', () => {
beforeEach(() => {
jest.spyOn(integrationSettingsForm.$form, 'checkValidity').mockReturnValue(true);
jest.spyOn(integrationSettingsForm.$form, 'submit');
});
it('should submit the form', async () => {
eventHub.$emit(SAVE_INTEGRATION_EVENT);
await waitForPromises();
expect(integrationSettingsForm.$form.submit).toHaveBeenCalled();
expect(integrationSettingsForm.$form.submit).toHaveBeenCalledTimes(1);
});
});
describe('when form is invalid', () => {
beforeEach(() => {
jest.spyOn(integrationSettingsForm.$form, 'checkValidity').mockReturnValue(false);
jest.spyOn(integrationSettingsForm.$form, 'submit');
});
it('should dispatch `setIsSaving` with `false` and not submit form', async () => {
const dispatchSpy = mockVuexDispatch();
eventHub.$emit(SAVE_INTEGRATION_EVENT);
await waitForPromises();
expect(dispatchSpy).toHaveBeenCalledWith('setIsSaving', false);
expect(integrationSettingsForm.$form.submit).not.toHaveBeenCalled();
});
});
});
});
}); });
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