diff options
Diffstat (limited to 'spec/frontend/integrations/edit/components/integration_form_spec.js')
-rw-r--r-- | spec/frontend/integrations/edit/components/integration_form_spec.js | 308 |
1 files changed, 275 insertions, 33 deletions
diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js index 0a9cbadb249..4c1394f3a87 100644 --- a/spec/frontend/integrations/edit/components/integration_form_spec.js +++ b/spec/frontend/integrations/edit/components/integration_form_spec.js @@ -1,6 +1,8 @@ +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import * as Sentry from '@sentry/browser'; import { setHTMLFixture } from 'helpers/fixtures'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; - import { mockIntegrationProps } from 'jest/integrations/edit/mock_data'; import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue'; import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue'; @@ -11,11 +13,27 @@ import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_field import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue'; import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.vue'; import TriggerFields from '~/integrations/edit/components/trigger_fields.vue'; -import { integrationLevels } from '~/integrations/constants'; +import waitForPromises from 'helpers/wait_for_promises'; +import { + integrationLevels, + I18N_SUCCESSFUL_CONNECTION_MESSAGE, + VALIDATE_INTEGRATION_FORM_EVENT, + I18N_DEFAULT_ERROR_MESSAGE, +} from '~/integrations/constants'; import { createStore } from '~/integrations/edit/store'; +import eventHub from '~/integrations/edit/event_hub'; +import httpStatus from '~/lib/utils/http_status'; + +jest.mock('~/integrations/edit/event_hub'); +jest.mock('@sentry/browser'); describe('IntegrationForm', () => { + const mockToastShow = jest.fn(); + let wrapper; + let dispatch; + let mockAxios; + let mockForm; const createComponent = ({ customStateProps = {}, @@ -23,12 +41,18 @@ describe('IntegrationForm', () => { initialState = {}, props = {}, } = {}) => { + const store = createStore({ + customState: { ...mockIntegrationProps, ...customStateProps }, + ...initialState, + }); + dispatch = jest.spyOn(store, 'dispatch').mockImplementation(); + wrapper = shallowMountExtended(IntegrationForm, { - propsData: { ...props }, - store: createStore({ - customState: { ...mockIntegrationProps, ...customStateProps }, - ...initialState, - }), + propsData: { ...props, formSelector: '.test' }, + provide: { + glFeatures: featureFlags, + }, + store, stubs: { OverrideDropdown, ActiveCheckbox, @@ -36,46 +60,42 @@ describe('IntegrationForm', () => { JiraTriggerFields, TriggerFields, }, - provide: { - glFeatures: featureFlags, + mocks: { + $toast: { + show: mockToastShow, + }, }, }); }; - afterEach(() => { - wrapper.destroy(); - }); + const createForm = ({ isValid = true } = {}) => { + mockForm = document.createElement('form'); + jest.spyOn(document, 'querySelector').mockReturnValue(mockForm); + jest.spyOn(mockForm, 'checkValidity').mockReturnValue(isValid); + jest.spyOn(mockForm, 'submit'); + }; const findOverrideDropdown = () => wrapper.findComponent(OverrideDropdown); const findActiveCheckbox = () => wrapper.findComponent(ActiveCheckbox); const findConfirmationModal = () => wrapper.findComponent(ConfirmationModal); const findResetConfirmationModal = () => wrapper.findComponent(ResetConfirmationModal); const findResetButton = () => wrapper.findByTestId('reset-button'); + const findSaveButton = () => wrapper.findByTestId('save-button'); + const findTestButton = () => wrapper.findByTestId('test-button'); const findJiraTriggerFields = () => wrapper.findComponent(JiraTriggerFields); const findJiraIssuesFields = () => wrapper.findComponent(JiraIssuesFields); const findTriggerFields = () => wrapper.findComponent(TriggerFields); - describe('template', () => { - describe('showActive is true', () => { - it('renders ActiveCheckbox', () => { - createComponent(); - - expect(findActiveCheckbox().exists()).toBe(true); - }); - }); - - describe('showActive is false', () => { - it('does not render ActiveCheckbox', () => { - createComponent({ - customStateProps: { - showActive: false, - }, - }); + beforeEach(() => { + mockAxios = new MockAdapter(axios); + }); - expect(findActiveCheckbox().exists()).toBe(false); - }); - }); + afterEach(() => { + wrapper.destroy(); + mockAxios.restore(); + }); + describe('template', () => { describe('integrationLevel is instance', () => { it('renders ConfirmationModal', () => { createComponent({ @@ -195,13 +215,29 @@ describe('IntegrationForm', () => { }); describe('type is "jira"', () => { - it('renders JiraTriggerFields', () => { + beforeEach(() => { + jest.spyOn(document, 'querySelector').mockReturnValue(document.createElement('form')); + createComponent({ - customStateProps: { type: 'jira' }, + customStateProps: { type: 'jira', testPath: '/test' }, }); + }); + it('renders JiraTriggerFields', () => { expect(findJiraTriggerFields().exists()).toBe(true); }); + + it('renders JiraIssuesFields', () => { + expect(findJiraIssuesFields().exists()).toBe(true); + }); + + describe('when JiraIssueFields emits `request-jira-issue-types` event', () => { + it('dispatches `requestJiraIssueTypes` action', () => { + findJiraIssuesFields().vm.$emit('request-jira-issue-types'); + + expect(dispatch).toHaveBeenCalledWith('requestJiraIssueTypes', expect.any(FormData)); + }); + }); }); describe('triggerEvents is present', () => { @@ -303,4 +339,210 @@ describe('IntegrationForm', () => { }); }); }); + + describe('ActiveCheckbox', () => { + describe.each` + showActive + ${true} + ${false} + `('when `showActive` is $showActive', ({ showActive }) => { + it(`${showActive ? 'renders' : 'does not render'} ActiveCheckbox`, () => { + createComponent({ + customStateProps: { + showActive, + }, + }); + + expect(findActiveCheckbox().exists()).toBe(showActive); + }); + }); + + describe.each` + formActive | novalidate + ${true} | ${null} + ${false} | ${'true'} + `( + 'when `toggle-integration-active` is emitted with $formActive', + ({ formActive, novalidate }) => { + beforeEach(async () => { + createForm(); + createComponent({ + customStateProps: { + showActive: true, + initialActivated: false, + }, + }); + + await findActiveCheckbox().vm.$emit('toggle-integration-active', formActive); + }); + + it(`sets noValidate to ${novalidate}`, () => { + expect(mockForm.getAttribute('novalidate')).toBe(novalidate); + }); + }, + ); + }); + + describe('when `save` button is clicked', () => { + describe('buttons', () => { + beforeEach(async () => { + createForm(); + createComponent({ + customStateProps: { + showActive: true, + canTest: true, + initialActivated: true, + }, + }); + + await findSaveButton().vm.$emit('click', new Event('click')); + }); + + it('sets save button `loading` prop to `true`', () => { + expect(findSaveButton().props('loading')).toBe(true); + }); + + it('sets test button `disabled` prop to `true`', () => { + expect(findTestButton().props('disabled')).toBe(true); + }); + }); + + describe.each` + checkValidityReturn | integrationActive + ${true} | ${false} + ${true} | ${true} + ${false} | ${false} + `( + 'when form is valid (checkValidity returns $checkValidityReturn and integrationActive is $integrationActive)', + ({ integrationActive, checkValidityReturn }) => { + beforeEach(async () => { + createForm({ isValid: checkValidityReturn }); + createComponent({ + customStateProps: { + showActive: true, + canTest: true, + initialActivated: integrationActive, + }, + }); + + await findSaveButton().vm.$emit('click', new Event('click')); + }); + + it('submit form', () => { + expect(mockForm.submit).toHaveBeenCalledTimes(1); + }); + }, + ); + + describe('when form is invalid (checkValidity returns false and integrationActive is true)', () => { + beforeEach(async () => { + createForm({ isValid: false }); + createComponent({ + customStateProps: { + showActive: true, + canTest: true, + initialActivated: true, + }, + }); + + await findSaveButton().vm.$emit('click', new Event('click')); + }); + + it('does not submit form', () => { + expect(mockForm.submit).not.toHaveBeenCalled(); + }); + + it('sets save button `loading` prop to `false`', () => { + expect(findSaveButton().props('loading')).toBe(false); + }); + + it('sets test button `disabled` prop to `false`', () => { + expect(findTestButton().props('disabled')).toBe(false); + }); + + it('emits `VALIDATE_INTEGRATION_FORM_EVENT`', () => { + expect(eventHub.$emit).toHaveBeenCalledWith(VALIDATE_INTEGRATION_FORM_EVENT); + }); + }); + }); + + describe('when `test` button is clicked', () => { + describe('when form is invalid', () => { + it('emits `VALIDATE_INTEGRATION_FORM_EVENT` event to the event hub', () => { + createForm({ isValid: false }); + createComponent({ + customStateProps: { + showActive: true, + canTest: true, + }, + }); + + findTestButton().vm.$emit('click', new Event('click')); + + expect(eventHub.$emit).toHaveBeenCalledWith(VALIDATE_INTEGRATION_FORM_EVENT); + }); + }); + + describe('when form is valid', () => { + const mockTestPath = '/test'; + + beforeEach(() => { + createForm({ isValid: true }); + createComponent({ + customStateProps: { + showActive: true, + canTest: true, + testPath: mockTestPath, + }, + }); + }); + + describe('buttons', () => { + beforeEach(async () => { + await findTestButton().vm.$emit('click', new Event('click')); + }); + + it('sets test button `loading` prop to `true`', () => { + expect(findTestButton().props('loading')).toBe(true); + }); + + it('sets save button `disabled` prop to `true`', () => { + expect(findSaveButton().props('disabled')).toBe(true); + }); + }); + + describe.each` + scenario | replyStatus | errorMessage | expectToast | expectSentry + ${'when "test settings" request fails'} | ${httpStatus.INTERNAL_SERVER_ERROR} | ${undefined} | ${I18N_DEFAULT_ERROR_MESSAGE} | ${true} + ${'when "test settings" returns an error'} | ${httpStatus.OK} | ${'an error'} | ${'an error'} | ${false} + ${'when "test settings" succeeds'} | ${httpStatus.OK} | ${undefined} | ${I18N_SUCCESSFUL_CONNECTION_MESSAGE} | ${false} + `('$scenario', ({ replyStatus, errorMessage, expectToast, expectSentry }) => { + beforeEach(async () => { + mockAxios.onPut(mockTestPath).replyOnce(replyStatus, { + error: Boolean(errorMessage), + message: errorMessage, + }); + + await findTestButton().vm.$emit('click', new Event('click')); + await waitForPromises(); + }); + + it(`calls toast with '${expectToast}'`, () => { + expect(mockToastShow).toHaveBeenCalledWith(expectToast); + }); + + it('sets `loading` prop of test button to `false`', () => { + expect(findTestButton().props('loading')).toBe(false); + }); + + it('sets save button `disabled` prop to `false`', () => { + expect(findSaveButton().props('disabled')).toBe(false); + }); + + it(`${expectSentry ? 'does' : 'does not'} capture exception in Sentry`, () => { + expect(Sentry.captureException).toHaveBeenCalledTimes(expectSentry ? 1 : 0); + }); + }); + }); + }); }); |