diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /spec/frontend/integrations | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'spec/frontend/integrations')
10 files changed, 386 insertions, 375 deletions
diff --git a/spec/frontend/integrations/edit/components/active_checkbox_spec.js b/spec/frontend/integrations/edit/components/active_checkbox_spec.js index df7ffd19747..0dc31616166 100644 --- a/spec/frontend/integrations/edit/components/active_checkbox_spec.js +++ b/spec/frontend/integrations/edit/components/active_checkbox_spec.js @@ -34,16 +34,22 @@ describe('ActiveCheckbox', () => { }); }); - describe('initialActivated is false', () => { - it('renders GlFormCheckbox as unchecked', () => { + describe('initialActivated is `false`', () => { + beforeEach(() => { createComponent({ initialActivated: false, }); + }); + it('renders GlFormCheckbox as unchecked', () => { expect(findGlFormCheckbox().exists()).toBe(true); expect(findGlFormCheckbox().vm.$attrs.checked).toBe(false); expect(findInputInCheckbox().attributes('disabled')).toBeUndefined(); }); + + it('emits `toggle-integration-active` event with `false` on mount', () => { + expect(wrapper.emitted('toggle-integration-active')[0]).toEqual([false]); + }); }); describe('initialActivated is true', () => { @@ -63,10 +69,21 @@ describe('ActiveCheckbox', () => { findInputInCheckbox().trigger('click'); await wrapper.vm.$nextTick(); - expect(findGlFormCheckbox().vm.$attrs.checked).toBe(false); }); }); + + it('emits `toggle-integration-active` event with `true` on mount', () => { + expect(wrapper.emitted('toggle-integration-active')[0]).toEqual([true]); + }); + + describe('on checkbox `change` event', () => { + it('emits `toggle-integration-active` event', () => { + findGlFormCheckbox().vm.$emit('change', false); + + expect(wrapper.emitted('toggle-integration-active')[1]).toEqual([false]); + }); + }); }); }); }); 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); + }); + }); + }); + }); }); diff --git a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js index 3a664b652ac..b5a8eed3598 100644 --- a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js +++ b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js @@ -1,10 +1,7 @@ import { GlFormCheckbox, GlFormInput } from '@gitlab/ui'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { - GET_JIRA_ISSUE_TYPES_EVENT, - VALIDATE_INTEGRATION_FORM_EVENT, -} from '~/integrations/constants'; +import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants'; import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue'; import eventHub from '~/integrations/edit/event_hub'; import { createStore } from '~/integrations/edit/store'; @@ -216,13 +213,11 @@ describe('JiraIssuesFields', () => { ); }); - it('emits "getJiraIssueTypes" to the eventHub when the jira-vulnerabilities component requests to fetch issue types', async () => { - const eventHubEmitSpy = jest.spyOn(eventHub, '$emit'); - + it('emits "request-jira-issue-types` when the jira-vulnerabilities component requests to fetch issue types', async () => { await setEnableCheckbox(true); - await findJiraForVulnerabilities().vm.$emit('request-get-issue-types'); + await findJiraForVulnerabilities().vm.$emit('request-jira-issue-types'); - expect(eventHubEmitSpy).toHaveBeenCalledWith(GET_JIRA_ISSUE_TYPES_EVENT); + expect(wrapper.emitted('request-jira-issue-types')).toHaveLength(1); }); }); diff --git a/spec/frontend/integrations/edit/mock_data.js b/spec/frontend/integrations/edit/mock_data.js index 27ba0768331..3c45ed0fb1b 100644 --- a/spec/frontend/integrations/edit/mock_data.js +++ b/spec/frontend/integrations/edit/mock_data.js @@ -14,3 +14,9 @@ export const mockIntegrationProps = { type: '', inheritFromId: 25, }; + +export const mockJiraIssueTypes = [ + { id: '1', name: 'issue', description: 'issue' }, + { id: '2', name: 'bug', description: 'bug' }, + { id: '3', name: 'epic', description: 'epic' }, +]; diff --git a/spec/frontend/integrations/edit/store/actions_spec.js b/spec/frontend/integrations/edit/store/actions_spec.js index e2f4c138ece..b413de2b286 100644 --- a/spec/frontend/integrations/edit/store/actions_spec.js +++ b/spec/frontend/integrations/edit/store/actions_spec.js @@ -1,8 +1,9 @@ +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; +import { I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } from '~/integrations/constants'; import { setOverride, - setIsSaving, - setIsTesting, setIsResetting, requestResetIntegration, receiveResetIntegrationSuccess, @@ -14,14 +15,21 @@ import { import * as types from '~/integrations/edit/store/mutation_types'; import createState from '~/integrations/edit/store/state'; import { refreshCurrentPage } from '~/lib/utils/url_utility'; +import { mockJiraIssueTypes } from '../mock_data'; jest.mock('~/lib/utils/url_utility'); describe('Integration form store actions', () => { let state; + let mockAxios; beforeEach(() => { state = createState(); + mockAxios = new MockAdapter(axios); + }); + + afterEach(() => { + mockAxios.restore(); }); describe('setOverride', () => { @@ -30,18 +38,6 @@ describe('Integration form store actions', () => { }); }); - describe('setIsSaving', () => { - it('should commit isSaving mutation', () => { - return testAction(setIsSaving, true, state, [{ type: types.SET_IS_SAVING, payload: true }]); - }); - }); - - describe('setIsTesting', () => { - it('should commit isTesting mutation', () => { - return testAction(setIsTesting, true, state, [{ type: types.SET_IS_TESTING, payload: true }]); - }); - }); - describe('setIsResetting', () => { it('should commit isResetting mutation', () => { return testAction(setIsResetting, true, state, [ @@ -75,11 +71,28 @@ describe('Integration form store actions', () => { }); describe('requestJiraIssueTypes', () => { - it('should commit SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE and SET_IS_LOADING_JIRA_ISSUE_TYPES mutations', () => { - return testAction(requestJiraIssueTypes, null, state, [ - { type: types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, payload: '' }, - { type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: true }, - ]); + describe.each` + scenario | responseCode | response | action + ${'when successful'} | ${200} | ${{ issuetypes: mockJiraIssueTypes }} | ${{ type: 'receiveJiraIssueTypesSuccess', payload: mockJiraIssueTypes }} + ${'when response has no issue types'} | ${200} | ${{ issuetypes: [] }} | ${{ type: 'receiveJiraIssueTypesError', payload: I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE }} + ${'when response includes error'} | ${200} | ${{ error: new Error() }} | ${{ type: 'receiveJiraIssueTypesError', payload: I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE }} + ${'when error occurs'} | ${500} | ${{}} | ${{ type: 'receiveJiraIssueTypesError', payload: expect.any(String) }} + `('$scenario', ({ responseCode, response, action }) => { + it(`should commit SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE and SET_IS_LOADING_JIRA_ISSUE_TYPES mutations, and dispatch ${action.type}`, () => { + mockAxios.onPut('/test').replyOnce(responseCode, response); + + return testAction( + requestJiraIssueTypes, + new FormData(), + { propsSource: { testPath: '/test' } }, + [ + // should clear the error messages and set the loading state + { type: types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, payload: '' }, + { type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: true }, + ], + [action], + ); + }); }); }); diff --git a/spec/frontend/integrations/edit/store/getters_spec.js b/spec/frontend/integrations/edit/store/getters_spec.js index ad7a887dff2..3353e0c84cc 100644 --- a/spec/frontend/integrations/edit/store/getters_spec.js +++ b/spec/frontend/integrations/edit/store/getters_spec.js @@ -1,11 +1,4 @@ -import { - currentKey, - isInheriting, - isDisabled, - propsSource, -} from '~/integrations/edit/store/getters'; -import * as types from '~/integrations/edit/store/mutation_types'; -import mutations from '~/integrations/edit/store/mutations'; +import { currentKey, isInheriting, propsSource } from '~/integrations/edit/store/getters'; import createState from '~/integrations/edit/store/state'; import { mockIntegrationProps } from '../mock_data'; @@ -52,29 +45,6 @@ describe('Integration form store getters', () => { }); }); - describe('isDisabled', () => { - it.each` - isSaving | isTesting | isResetting | expected - ${false} | ${false} | ${false} | ${false} - ${true} | ${false} | ${false} | ${true} - ${false} | ${true} | ${false} | ${true} - ${false} | ${false} | ${true} | ${true} - ${false} | ${true} | ${true} | ${true} - ${true} | ${false} | ${true} | ${true} - ${true} | ${true} | ${false} | ${true} - ${true} | ${true} | ${true} | ${true} - `( - 'when isSaving = $isSaving, isTesting = $isTesting, isResetting = $isResetting then isDisabled = $expected', - ({ isSaving, isTesting, isResetting, expected }) => { - mutations[types.SET_IS_SAVING](state, isSaving); - mutations[types.SET_IS_TESTING](state, isTesting); - mutations[types.SET_IS_RESETTING](state, isResetting); - - expect(isDisabled(state)).toBe(expected); - }, - ); - }); - describe('propsSource', () => { beforeEach(() => { state.defaultState = defaultState; diff --git a/spec/frontend/integrations/edit/store/mutations_spec.js b/spec/frontend/integrations/edit/store/mutations_spec.js index 18faa2f6bba..641547550d1 100644 --- a/spec/frontend/integrations/edit/store/mutations_spec.js +++ b/spec/frontend/integrations/edit/store/mutations_spec.js @@ -17,22 +17,6 @@ describe('Integration form store mutations', () => { }); }); - describe(`${types.SET_IS_SAVING}`, () => { - it('sets isSaving', () => { - mutations[types.SET_IS_SAVING](state, true); - - expect(state.isSaving).toBe(true); - }); - }); - - describe(`${types.SET_IS_TESTING}`, () => { - it('sets isTesting', () => { - mutations[types.SET_IS_TESTING](state, true); - - expect(state.isTesting).toBe(true); - }); - }); - describe(`${types.SET_IS_RESETTING}`, () => { it('sets isResetting', () => { mutations[types.SET_IS_RESETTING](state, true); diff --git a/spec/frontend/integrations/edit/store/state_spec.js b/spec/frontend/integrations/edit/store/state_spec.js index 6cd84836395..5582be7fd3c 100644 --- a/spec/frontend/integrations/edit/store/state_spec.js +++ b/spec/frontend/integrations/edit/store/state_spec.js @@ -6,7 +6,6 @@ describe('Integration form state factory', () => { defaultState: null, customState: {}, isSaving: false, - isTesting: false, isResetting: false, override: false, isLoadingJiraIssueTypes: false, diff --git a/spec/frontend/integrations/integration_settings_form_spec.js b/spec/frontend/integrations/integration_settings_form_spec.js deleted file mode 100644 index c35d178e518..00000000000 --- a/spec/frontend/integrations/integration_settings_form_spec.js +++ /dev/null @@ -1,248 +0,0 @@ -import MockAdaptor from 'axios-mock-adapter'; -import IntegrationSettingsForm from '~/integrations/integration_settings_form'; -import eventHub from '~/integrations/edit/event_hub'; -import axios from '~/lib/utils/axios_utils'; -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('lodash/delay', () => (callback) => callback()); - -const FIXTURE = 'services/edit_service.html'; - -describe('IntegrationSettingsForm', () => { - let integrationSettingsForm; - - const mockStoreDispatch = () => jest.spyOn(integrationSettingsForm.vue.$store, 'dispatch'); - - beforeEach(() => { - loadFixtures(FIXTURE); - - integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); - integrationSettingsForm.init(); - }); - - describe('constructor', () => { - it('should initialize form element refs on class object', () => { - expect(integrationSettingsForm.$form).toBeDefined(); - expect(integrationSettingsForm.$form.nodeName).toBe('FORM'); - expect(integrationSettingsForm.formActive).toBeDefined(); - }); - - it('should initialize form metadata on class object', () => { - expect(integrationSettingsForm.testEndPoint).toBeDefined(); - }); - }); - - describe('event handling', () => { - let mockAxios; - - beforeEach(() => { - 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`', () => { - eventHub.$emit(TOGGLE_INTEGRATION_EVENT, true); - - expect(integrationSettingsForm.$form.getAttribute('novalidate')).toBe(null); - }); - - it('should set `novalidate` attribute to form when called with `false`', () => { - eventHub.$emit(TOGGLE_INTEGRATION_EVENT, false); - - expect(integrationSettingsForm.$form.getAttribute('novalidate')).toBe('novalidate'); - }); - }); - - describe('when event hub receives `TEST_INTEGRATION_EVENT`', () => { - describe('when form is valid', () => { - beforeEach(() => { - jest.spyOn(integrationSettingsForm.$form, 'checkValidity').mockReturnValue(true); - }); - - it('should make an ajax request with provided `formData`', async () => { - eventHub.$emit(TEST_INTEGRATION_EVENT); - await waitForPromises(); - - expect(axios.put).toHaveBeenCalledWith( - integrationSettingsForm.testEndPoint, - new FormData(integrationSettingsForm.$form), - ); - }); - - it('should show success message if test is successful', async () => { - jest.spyOn(integrationSettingsForm.$form, 'submit').mockImplementation(() => {}); - - mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, { - error: false, - }); - - eventHub.$emit(TEST_INTEGRATION_EVENT); - await waitForPromises(); - - expect(toast).toHaveBeenCalledWith(I18N_SUCCESSFUL_CONNECTION_MESSAGE); - }); - - it('should show error message if ajax request responds with test error', async () => { - const errorMessage = 'Test failed.'; - const serviceResponse = 'some error'; - - mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, { - error: true, - message: errorMessage, - service_response: serviceResponse, - test_failed: false, - }); - - eventHub.$emit(TEST_INTEGRATION_EVENT); - await waitForPromises(); - - expect(toast).toHaveBeenCalledWith(`${errorMessage} ${serviceResponse}`); - }); - - it('should show error message if ajax request failed', async () => { - mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError(); - - eventHub.$emit(TEST_INTEGRATION_EVENT); - await waitForPromises(); - - expect(toast).toHaveBeenCalledWith(I18N_DEFAULT_ERROR_MESSAGE); - }); - - it('should always dispatch `setIsTesting` with `false` once request is completed', async () => { - const dispatchSpy = mockStoreDispatch(); - mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError(); - - eventHub.$emit(TEST_INTEGRATION_EVENT); - await waitForPromises(); - - expect(dispatchSpy).toHaveBeenCalledWith('setIsTesting', false); - }); - }); - - describe('when form is invalid', () => { - beforeEach(() => { - jest.spyOn(integrationSettingsForm.$form, 'checkValidity').mockReturnValue(false); - jest.spyOn(integrationSettingsForm, 'testSettings'); - }); - - it('should dispatch `setIsTesting` with `false` and not call `testSettings`', async () => { - const dispatchSpy = mockStoreDispatch(); - - eventHub.$emit(TEST_INTEGRATION_EVENT); - await waitForPromises(); - - expect(dispatchSpy).toHaveBeenCalledWith('setIsTesting', false); - expect(integrationSettingsForm.testSettings).not.toHaveBeenCalled(); - }); - }); - }); - - describe('when event hub receives `GET_JIRA_ISSUE_TYPES_EVENT`', () => { - it('should always dispatch `requestJiraIssueTypes`', () => { - const dispatchSpy = mockStoreDispatch(); - mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError(); - - eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT); - - expect(dispatchSpy).toHaveBeenCalledWith('requestJiraIssueTypes'); - }); - - it('should make an ajax request with provided `formData`', () => { - eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT); - - expect(axios.put).toHaveBeenCalledWith( - integrationSettingsForm.testEndPoint, - new FormData(integrationSettingsForm.$form), - ); - }); - - it('should dispatch `receiveJiraIssueTypesSuccess` with the correct payload if ajax request is successful', async () => { - const dispatchSpy = mockStoreDispatch(); - const mockData = ['ISSUE', 'EPIC']; - mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, { - error: false, - issuetypes: mockData, - }); - - eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT); - await waitForPromises(); - - expect(dispatchSpy).toHaveBeenCalledWith('receiveJiraIssueTypesSuccess', mockData); - }); - - it.each(['Custom error message here', undefined])( - 'should dispatch "receiveJiraIssueTypesError" with a message if the backend responds with error', - async (responseErrorMessage) => { - const dispatchSpy = mockStoreDispatch(); - - const expectedErrorMessage = - responseErrorMessage || I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE; - mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, { - error: true, - message: responseErrorMessage, - }); - - eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT); - await waitForPromises(); - - expect(dispatchSpy).toHaveBeenCalledWith( - 'receiveJiraIssueTypesError', - expectedErrorMessage, - ); - }, - ); - }); - - 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 = mockStoreDispatch(); - - eventHub.$emit(SAVE_INTEGRATION_EVENT); - - await waitForPromises(); - - expect(dispatchSpy).toHaveBeenCalledWith('setIsSaving', false); - expect(integrationSettingsForm.$form.submit).not.toHaveBeenCalled(); - }); - }); - }); - }); -}); diff --git a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js index ae89d05cead..8abd83887f7 100644 --- a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js +++ b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js @@ -8,6 +8,7 @@ import IntegrationOverrides from '~/integrations/overrides/components/integratio import axios from '~/lib/utils/axios_utils'; import httpStatus from '~/lib/utils/http_status'; import ProjectAvatar from '~/vue_shared/components/project_avatar.vue'; +import UrlSync from '~/vue_shared/components/url_sync.vue'; const mockOverrides = Array(DEFAULT_PER_PAGE * 3) .fill(1) @@ -26,9 +27,10 @@ describe('IntegrationOverrides', () => { overridesPath: 'mock/overrides', }; - const createComponent = ({ mountFn = shallowMount } = {}) => { + const createComponent = ({ mountFn = shallowMount, stubs } = {}) => { wrapper = mountFn(IntegrationOverrides, { propsData: defaultProps, + stubs, }); }; @@ -127,27 +129,58 @@ describe('IntegrationOverrides', () => { }); describe('pagination', () => { - it('triggers fetch when `input` event is emitted', async () => { - createComponent(); - jest.spyOn(axios, 'get'); - await waitForPromises(); + describe('when total items does not exceed the page limit', () => { + it('does not render', async () => { + mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, [mockOverrides[0]], { + 'X-TOTAL': DEFAULT_PER_PAGE - 1, + 'X-PAGE': 1, + }); + + createComponent(); + + // wait for initial load + await waitForPromises(); - await findPagination().vm.$emit('input', 2); - expect(axios.get).toHaveBeenCalledWith(defaultProps.overridesPath, { - params: { page: 2, per_page: DEFAULT_PER_PAGE }, + expect(findPagination().exists()).toBe(false); }); }); - it('does not render with <=1 page', async () => { - mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, [mockOverrides[0]], { - 'X-TOTAL': 1, - 'X-PAGE': 1, + describe('when total items exceeds the page limit', () => { + const mockPage = 2; + + beforeEach(async () => { + createComponent({ stubs: { UrlSync } }); + mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, [mockOverrides[0]], { + 'X-TOTAL': DEFAULT_PER_PAGE * 2, + 'X-PAGE': mockPage, + }); + + // wait for initial load + await waitForPromises(); }); - createComponent(); - await waitForPromises(); + it('renders', () => { + expect(findPagination().exists()).toBe(true); + }); - expect(findPagination().exists()).toBe(false); + describe('when navigating to a page', () => { + beforeEach(async () => { + jest.spyOn(axios, 'get'); + + // trigger a page change + await findPagination().vm.$emit('input', mockPage); + }); + + it('performs GET request with correct params', () => { + expect(axios.get).toHaveBeenCalledWith(defaultProps.overridesPath, { + params: { page: mockPage, per_page: DEFAULT_PER_PAGE }, + }); + }); + + it('updates `page` URL parameter', () => { + expect(window.location.search).toBe(`?page=${mockPage}`); + }); + }); }); }); }); |