diff options
Diffstat (limited to 'spec/frontend/alerts_settings/components/alerts_settings_form_spec.js')
-rw-r--r-- | spec/frontend/alerts_settings/components/alerts_settings_form_spec.js | 258 |
1 files changed, 161 insertions, 97 deletions
diff --git a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js index d2dcff14432..9912ac433a5 100644 --- a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js +++ b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js @@ -1,5 +1,7 @@ import { GlForm, GlFormSelect, GlFormInput, GlToggle, GlFormTextarea, GlTab } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import MappingBuilder from '~/alerts_settings/components/alert_mapping_builder.vue'; import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue'; @@ -8,48 +10,52 @@ import alertFields from '../mocks/alert_fields.json'; import parsedMapping from '../mocks/parsed_mapping.json'; import { defaultAlertSettingsConfig } from './util'; +const scrollIntoViewMock = jest.fn(); +HTMLElement.prototype.scrollIntoView = scrollIntoViewMock; + describe('AlertsSettingsForm', () => { let wrapper; const mockToastShow = jest.fn(); const createComponent = ({ data = {}, props = {}, multiIntegrations = true } = {}) => { - wrapper = mount(AlertsSettingsForm, { - data() { - return { ...data }; - }, - propsData: { - loading: false, - canAddIntegration: true, - ...props, - }, - provide: { - ...defaultAlertSettingsConfig, - multiIntegrations, - }, - mocks: { - $apollo: { - query: jest.fn(), + wrapper = extendedWrapper( + mount(AlertsSettingsForm, { + data() { + return { ...data }; }, - $toast: { - show: mockToastShow, + propsData: { + loading: false, + canAddIntegration: true, + ...props, }, - }, - }); + provide: { + ...defaultAlertSettingsConfig, + multiIntegrations, + }, + mocks: { + $apollo: { + query: jest.fn(), + }, + $toast: { + show: mockToastShow, + }, + }, + }), + ); }; const findForm = () => wrapper.findComponent(GlForm); const findSelect = () => wrapper.findComponent(GlFormSelect); const findFormFields = () => wrapper.findAllComponents(GlFormInput); const findFormToggle = () => wrapper.findComponent(GlToggle); - const findSamplePayloadSection = () => wrapper.find('[data-testid="sample-payload-section"]'); - const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`); + const findSamplePayloadSection = () => wrapper.findByTestId('sample-payload-section'); const findMappingBuilder = () => wrapper.findComponent(MappingBuilder); - const findSubmitButton = () => wrapper.find(`[type = "submit"]`); - const findMultiSupportText = () => - wrapper.find(`[data-testid="multi-integrations-not-supported"]`); - const findJsonTestSubmit = () => wrapper.find(`[data-testid="send-test-alert"]`); + + const findSubmitButton = () => wrapper.findByTestId('integration-form-submit'); + const findMultiSupportText = () => wrapper.findByTestId('multi-integrations-not-supported'); + const findJsonTestSubmit = () => wrapper.findByTestId('send-test-alert'); const findJsonTextArea = () => wrapper.find(`[id = "test-payload"]`); - const findActionBtn = () => wrapper.find(`[data-testid="payload-action-btn"]`); + const findActionBtn = () => wrapper.findByTestId('payload-action-btn'); const findTabs = () => wrapper.findAllComponents(GlTab); afterEach(() => { @@ -74,10 +80,6 @@ describe('AlertsSettingsForm', () => { createComponent(); }); - it('renders the initial template', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - it('render the initial form with only an integration type dropdown', () => { expect(findForm().exists()).toBe(true); expect(findSelect().exists()).toBe(true); @@ -151,29 +153,28 @@ describe('AlertsSettingsForm', () => { findMappingBuilder().vm.$emit('onMappingUpdate', sampleMapping); findForm().trigger('submit'); - expect(wrapper.emitted('create-new-integration')[0]).toEqual([ - { - type: typeSet.http, - variables: { - name: integrationName, - active: true, - payloadAttributeMappings: sampleMapping, - payloadExample: '{}', - }, + expect(wrapper.emitted('create-new-integration')[0][0]).toMatchObject({ + type: typeSet.http, + variables: { + name: integrationName, + active: true, + payloadAttributeMappings: sampleMapping, + payloadExample: '{}', }, - ]); + }); }); it('update', () => { createComponent({ data: { - selectedIntegration: typeSet.http, - currentIntegration: { id: '1', name: 'Test integration pre' }, + integrationForm: { id: '1', name: 'Test integration pre', type: typeSet.http }, + currentIntegration: { id: '1' }, }, props: { loading: false, }, }); + const updatedIntegrationName = 'Test integration post'; enableIntegration(0, updatedIntegrationName); @@ -181,21 +182,16 @@ describe('AlertsSettingsForm', () => { expect(submitBtn.exists()).toBe(true); expect(submitBtn.text()).toBe('Save integration'); - findForm().trigger('submit'); - - expect(wrapper.emitted('update-integration')[0]).toEqual( - expect.arrayContaining([ - { - type: typeSet.http, - variables: { - name: updatedIntegrationName, - active: true, - payloadAttributeMappings: [], - payloadExample: '{}', - }, - }, - ]), - ); + submitBtn.trigger('click'); + expect(wrapper.emitted('update-integration')[0][0]).toMatchObject({ + type: typeSet.http, + variables: { + name: updatedIntegrationName, + active: true, + payloadAttributeMappings: [], + payloadExample: '{}', + }, + }); }); }); @@ -211,16 +207,17 @@ describe('AlertsSettingsForm', () => { findForm().trigger('submit'); - expect(wrapper.emitted('create-new-integration')[0]).toEqual([ - { type: typeSet.prometheus, variables: { apiUrl, active: true } }, - ]); + expect(wrapper.emitted('create-new-integration')[0][0]).toMatchObject({ + type: typeSet.prometheus, + variables: { apiUrl, active: true }, + }); }); it('update', () => { createComponent({ data: { - selectedIntegration: typeSet.prometheus, - currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' }, + integrationForm: { id: '1', apiUrl: 'https://test-pre.com', type: typeSet.prometheus }, + currentIntegration: { id: '1' }, }, props: { loading: false, @@ -236,9 +233,10 @@ describe('AlertsSettingsForm', () => { findForm().trigger('submit'); - expect(wrapper.emitted('update-integration')[0]).toEqual([ - { type: typeSet.prometheus, variables: { apiUrl, active: true } }, - ]); + expect(wrapper.emitted('update-integration')[0][0]).toMatchObject({ + type: typeSet.prometheus, + variables: { apiUrl, active: true }, + }); }); }); }); @@ -247,7 +245,6 @@ describe('AlertsSettingsForm', () => { beforeEach(() => { createComponent({ data: { - selectedIntegration: typeSet.http, currentIntegration: { id: '1', name: 'Test' }, active: true, }, @@ -262,7 +259,7 @@ describe('AlertsSettingsForm', () => { await findJsonTextArea().setValue('Invalid JSON'); jest.runAllTimers(); - await wrapper.vm.$nextTick(); + await nextTick(); const jsonTestSubmit = findJsonTestSubmit(); expect(jsonTestSubmit.exists()).toBe(true); @@ -275,7 +272,7 @@ describe('AlertsSettingsForm', () => { await findJsonTextArea().setValue('{ "value": "value" }'); jest.runAllTimers(); - await wrapper.vm.$nextTick(); + await nextTick(); expect(findJsonTestSubmit().props('disabled')).toBe(false); }); }); @@ -283,14 +280,13 @@ describe('AlertsSettingsForm', () => { describe('Test payload section for HTTP integration', () => { const validSamplePayload = JSON.stringify(alertFields); const emptySamplePayload = '{}'; - beforeEach(() => { createComponent({ + multiIntegrations: true, data: { + integrationForm: { type: typeSet.http }, currentIntegration: { - type: typeSet.http, - payloadExample: validSamplePayload, - payloadAttributeMappings: [], + payloadExample: emptySamplePayload, }, active: false, resetPayloadAndMappingConfirmed: false, @@ -300,25 +296,25 @@ describe('AlertsSettingsForm', () => { }); describe.each` - active | resetPayloadAndMappingConfirmed | disabled - ${true} | ${true} | ${undefined} - ${false} | ${true} | ${'disabled'} - ${true} | ${false} | ${'disabled'} - ${false} | ${false} | ${'disabled'} - `('', ({ active, resetPayloadAndMappingConfirmed, disabled }) => { + payload | resetPayloadAndMappingConfirmed | disabled + ${validSamplePayload} | ${true} | ${undefined} + ${emptySamplePayload} | ${true} | ${undefined} + ${validSamplePayload} | ${false} | ${'disabled'} + ${emptySamplePayload} | ${false} | ${undefined} + `('', ({ payload, resetPayloadAndMappingConfirmed, disabled }) => { const payloadResetMsg = resetPayloadAndMappingConfirmed ? 'was confirmed' : 'was not confirmed'; const enabledState = disabled === 'disabled' ? 'disabled' : 'enabled'; - const activeState = active ? 'active' : 'not active'; + const validPayloadMsg = payload === emptySamplePayload ? 'not valid' : 'valid'; - it(`textarea should be ${enabledState} when payload reset ${payloadResetMsg} and current integration is ${activeState}`, async () => { + it(`textarea should be ${enabledState} when payload reset ${payloadResetMsg} and payload is ${validPayloadMsg}`, async () => { wrapper.setData({ - selectedIntegration: typeSet.http, - active, + currentIntegration: { payloadExample: payload }, resetPayloadAndMappingConfirmed, }); - await wrapper.vm.$nextTick(); + + await nextTick(); expect(findSamplePayloadSection().find(GlFormTextarea).attributes('disabled')).toBe( disabled, ); @@ -329,9 +325,9 @@ describe('AlertsSettingsForm', () => { describe.each` resetPayloadAndMappingConfirmed | payloadExample | caption ${false} | ${validSamplePayload} | ${'Edit payload'} - ${true} | ${emptySamplePayload} | ${'Parse payload for custom mapping'} - ${true} | ${validSamplePayload} | ${'Parse payload for custom mapping'} - ${false} | ${emptySamplePayload} | ${'Parse payload for custom mapping'} + ${true} | ${emptySamplePayload} | ${'Parse payload fields'} + ${true} | ${validSamplePayload} | ${'Parse payload fields'} + ${false} | ${emptySamplePayload} | ${'Parse payload fields'} `('', ({ resetPayloadAndMappingConfirmed, payloadExample, caption }) => { const samplePayloadMsg = payloadExample ? 'was provided' : 'was not provided'; const payloadResetMsg = resetPayloadAndMappingConfirmed @@ -340,16 +336,12 @@ describe('AlertsSettingsForm', () => { it(`shows ${caption} button when sample payload ${samplePayloadMsg} and payload reset ${payloadResetMsg}`, async () => { wrapper.setData({ - selectedIntegration: typeSet.http, currentIntegration: { payloadExample, - type: typeSet.http, - active: true, - payloadAttributeMappings: [], }, resetPayloadAndMappingConfirmed, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(findActionBtn().text()).toBe(caption); }); }); @@ -358,7 +350,6 @@ describe('AlertsSettingsForm', () => { describe('Parsing payload', () => { beforeEach(() => { wrapper.setData({ - selectedIntegration: typeSet.http, resetPayloadAndMappingConfirmed: true, }); }); @@ -398,11 +389,12 @@ describe('AlertsSettingsForm', () => { ${true} | ${false} | ${1} | ${false} ${false} | ${true} | ${1} | ${false} `('', ({ alertFieldsProvided, multiIntegrations, integrationOption, visible }) => { - const visibleMsg = visible ? 'is rendered' : 'is not rendered'; - const alertFieldsMsg = alertFieldsProvided ? 'are provided' : 'are not provided'; + const visibleMsg = visible ? 'rendered' : 'not rendered'; + const alertFieldsMsg = alertFieldsProvided ? 'provided' : 'not provided'; const integrationType = integrationOption === 1 ? typeSet.http : typeSet.prometheus; + const multiIntegrationsEnabled = multiIntegrations ? 'enabled' : 'not enabled'; - it(`${visibleMsg} when integration type is ${integrationType} and alert fields ${alertFieldsMsg}`, async () => { + it(`is ${visibleMsg} when multiIntegrations are ${multiIntegrationsEnabled}, integration type is ${integrationType} and alert fields are ${alertFieldsMsg}`, async () => { createComponent({ multiIntegrations, props: { @@ -411,8 +403,80 @@ describe('AlertsSettingsForm', () => { }); await selectOptionAtIndex(integrationOption); - expect(findMappingBuilderSection().exists()).toBe(visible); + expect(findMappingBuilder().exists()).toBe(visible); + }); + }); + }); + + describe('Form validation', () => { + beforeEach(() => { + createComponent(); + }); + + it('should not be able to submit when no integration type is selected', async () => { + await selectOptionAtIndex(0); + + expect(findSubmitButton().attributes('disabled')).toBe('disabled'); + }); + + it('should not be able to submit when HTTP integration form is invalid', async () => { + await selectOptionAtIndex(1); + await findFormFields().at(0).vm.$emit('input', ''); + expect(findSubmitButton().attributes('disabled')).toBe('disabled'); + }); + + it('should be able to submit when HTTP integration form is valid', async () => { + await selectOptionAtIndex(1); + await findFormFields().at(0).vm.$emit('input', 'Name'); + expect(findSubmitButton().attributes('disabled')).toBe(undefined); + }); + + it('should not be able to submit when Prometheus integration form is invalid', async () => { + await selectOptionAtIndex(2); + await findFormFields().at(0).vm.$emit('input', ''); + + expect(findSubmitButton().attributes('disabled')).toBe('disabled'); + }); + + it('should be able to submit when Prometheus integration form is valid', async () => { + await selectOptionAtIndex(2); + await findFormFields().at(0).vm.$emit('input', 'http://valid.url'); + + expect(findSubmitButton().attributes('disabled')).toBe(undefined); + }); + + it('should be able to submit when form is dirty', async () => { + wrapper.setData({ + currentIntegration: { type: typeSet.http, name: 'Existing integration' }, + }); + await nextTick(); + await findFormFields().at(0).vm.$emit('input', 'Updated name'); + + expect(findSubmitButton().attributes('disabled')).toBe(undefined); + }); + + it('should not be able to submit when form is pristine', async () => { + wrapper.setData({ + currentIntegration: { type: typeSet.http, name: 'Existing integration' }, }); + await nextTick(); + + expect(findSubmitButton().attributes('disabled')).toBe('disabled'); + }); + + it('should disable submit button after click on validation failure', async () => { + await selectOptionAtIndex(1); + findSubmitButton().trigger('click'); + await nextTick(); + + expect(findSubmitButton().attributes('disabled')).toBe('disabled'); + }); + + it('should scroll to invalid field on validation failure', async () => { + await selectOptionAtIndex(1); + findSubmitButton().trigger('click'); + + expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth', block: 'center' }); }); }); }); |