summaryrefslogtreecommitdiff
path: root/spec/frontend/alerts_settings
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/alerts_settings')
-rw-r--r--spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap524
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_form_spec.js258
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js120
3 files changed, 236 insertions, 666 deletions
diff --git a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap
deleted file mode 100644
index 1f8429af7dd..00000000000
--- a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap
+++ /dev/null
@@ -1,524 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`AlertsSettingsForm with default values renders the initial template 1`] = `
-<form
- class="gl-mt-6"
->
- <div
- class="tabs gl-tabs"
- id="__BVID__6"
- >
- <!---->
- <div
- class=""
- >
- <ul
- class="nav gl-tabs-nav"
- id="__BVID__6__BV_tab_controls_"
- role="tablist"
- >
- <!---->
- <li
- class="nav-item"
- role="presentation"
- >
- <a
- aria-controls="__BVID__8"
- aria-posinset="1"
- aria-selected="true"
- aria-setsize="3"
- class="nav-link active gl-tab-nav-item gl-tab-nav-item-active gl-tab-nav-item-active-indigo"
- href="#"
- id="__BVID__8___BV_tab_button__"
- role="tab"
- target="_self"
- >
- Configure details
- </a>
- </li>
- <li
- class="nav-item"
- role="presentation"
- >
- <a
- aria-controls="__BVID__19"
- aria-disabled="true"
- aria-posinset="2"
- aria-selected="false"
- aria-setsize="3"
- class="nav-link disabled disabled gl-tab-nav-item"
- href="#"
- id="__BVID__19___BV_tab_button__"
- role="tab"
- tabindex="-1"
- target="_self"
- >
- View credentials
- </a>
- </li>
- <li
- class="nav-item"
- role="presentation"
- >
- <a
- aria-controls="__BVID__41"
- aria-disabled="true"
- aria-posinset="3"
- aria-selected="false"
- aria-setsize="3"
- class="nav-link disabled disabled gl-tab-nav-item"
- href="#"
- id="__BVID__41___BV_tab_button__"
- role="tab"
- tabindex="-1"
- target="_self"
- >
- Send test alert
- </a>
- </li>
- <!---->
- </ul>
- </div>
- <div
- class="tab-content gl-tab-content"
- id="__BVID__6__BV_tab_container_"
- >
- <transition-stub
- css="true"
- enteractiveclass=""
- enterclass=""
- entertoclass="show"
- leaveactiveclass=""
- leaveclass="show"
- leavetoclass=""
- mode="out-in"
- name=""
- >
- <div
- aria-hidden="false"
- aria-labelledby="__BVID__8___BV_tab_button__"
- class="tab-pane active"
- id="__BVID__8"
- role="tabpanel"
- style=""
- >
- <div
- class="form-group gl-form-group"
- id="integration-type"
- role="group"
- >
- <label
- class="d-block col-form-label"
- for="integration-type"
- id="integration-type__BV_label_"
- >
- 1.Select integration type
- </label>
- <div
- class="bv-no-focus-ring"
- >
- <select
- class="gl-form-select gl-max-w-full custom-select"
- id="__BVID__13"
- >
- <option
- value=""
- >
- Select integration type
- </option>
- <option
- value="HTTP"
- >
- HTTP Endpoint
- </option>
- <option
- value="PROMETHEUS"
- >
- External Prometheus
- </option>
- </select>
-
- <!---->
- <!---->
- <!---->
- <!---->
- </div>
- </div>
-
- <div
- class="gl-mt-3"
- >
- <!---->
-
- <label
- class="gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content gl-my-4 gl-font-weight-normal"
- >
- <span
- class="gl-toggle-wrapper"
- >
- <span
- class="gl-toggle-label"
- data-testid="toggle-label"
- >
- Active
- </span>
-
- <!---->
-
- <button
- aria-label="Active"
- class="gl-toggle"
- role="switch"
- type="button"
- >
- <span
- class="toggle-icon"
- >
- <svg
- aria-hidden="true"
- class="gl-icon s16"
- data-testid="close-icon"
- >
- <use
- href="#close"
- />
- </svg>
- </span>
- </button>
- </span>
-
- <!---->
- </label>
-
- <!---->
-
- <!---->
- </div>
-
- <div
- class="gl-display-flex gl-justify-content-start gl-py-3"
- >
- <button
- class="btn js-no-auto-disable btn-confirm btn-md gl-button"
- data-testid="integration-form-submit"
- type="submit"
- >
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Save integration
-
- </span>
- </button>
-
- <button
- class="btn gl-ml-3 js-no-auto-disable btn-default btn-md gl-button"
- type="reset"
- >
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
- Cancel and close
- </span>
- </button>
- </div>
- </div>
- </transition-stub>
-
- <transition-stub
- css="true"
- enteractiveclass=""
- enterclass=""
- entertoclass="show"
- leaveactiveclass=""
- leaveclass="show"
- leavetoclass=""
- mode="out-in"
- name=""
- >
- <div
- aria-hidden="true"
- aria-labelledby="__BVID__19___BV_tab_button__"
- class="tab-pane disabled"
- id="__BVID__19"
- role="tabpanel"
- style="display: none;"
- >
- <span>
- Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the
- <a
- class="gl-link gl-display-inline-block"
- href="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html"
- rel="noopener noreferrer"
- target="_blank"
- >
- GitLab documentation
- </a>
- to learn more about configuring your endpoint.
- </span>
-
- <fieldset
- class="form-group gl-form-group"
- id="integration-webhook"
- >
- <!---->
- <div
- class="bv-no-focus-ring"
- role="group"
- tabindex="-1"
- >
- <div
- class="gl-my-4"
- >
- <span
- class="gl-font-weight-bold"
- >
-
- Webhook URL
-
- </span>
-
- <div
- id="url"
- readonly="readonly"
- >
- <div
- class="input-group"
- role="group"
- >
- <!---->
- <!---->
-
- <input
- class="gl-form-input form-control"
- id="url"
- readonly="readonly"
- type="text"
- />
-
- <div
- class="input-group-append"
- >
- <button
- aria-label="Copy this value"
- class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon"
- data-clipboard-text=""
- title="Copy"
- type="button"
- >
- <!---->
-
- <svg
- aria-hidden="true"
- class="gl-button-icon gl-icon s16"
- data-testid="copy-to-clipboard-icon"
- >
- <use
- href="#copy-to-clipboard"
- />
- </svg>
-
- <!---->
- </button>
- </div>
- <!---->
- </div>
- </div>
- </div>
-
- <div
- class="gl-my-4"
- >
- <span
- class="gl-font-weight-bold"
- >
-
- Authorization key
-
- </span>
-
- <div
- class="gl-mb-3"
- id="authorization-key"
- readonly="readonly"
- >
- <div
- class="input-group"
- role="group"
- >
- <!---->
- <!---->
-
- <input
- class="gl-form-input form-control"
- id="authorization-key"
- readonly="readonly"
- type="text"
- />
-
- <div
- class="input-group-append"
- >
- <button
- aria-label="Copy this value"
- class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon"
- data-clipboard-text=""
- title="Copy"
- type="button"
- >
- <!---->
-
- <svg
- aria-hidden="true"
- class="gl-button-icon gl-icon s16"
- data-testid="copy-to-clipboard-icon"
- >
- <use
- href="#copy-to-clipboard"
- />
- </svg>
-
- <!---->
- </button>
- </div>
- <!---->
- </div>
- </div>
- </div>
- <!---->
- <!---->
- <!---->
- </div>
- </fieldset>
-
- <button
- class="btn btn-danger btn-md disabled gl-button"
- disabled="disabled"
- type="button"
- >
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Reset Key
-
- </span>
- </button>
-
- <button
- class="btn gl-ml-3 js-no-auto-disable btn-default btn-md gl-button"
- type="reset"
- >
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
- Cancel and close
- </span>
- </button>
-
- <!---->
- </div>
- </transition-stub>
-
- <transition-stub
- css="true"
- enteractiveclass=""
- enterclass=""
- entertoclass="show"
- leaveactiveclass=""
- leaveclass="show"
- leavetoclass=""
- mode="out-in"
- name=""
- >
- <div
- aria-hidden="true"
- aria-labelledby="__BVID__41___BV_tab_button__"
- class="tab-pane disabled"
- id="__BVID__41"
- role="tabpanel"
- style="display: none;"
- >
- <fieldset
- class="form-group gl-form-group"
- id="test-integration"
- >
- <!---->
- <div
- class="bv-no-focus-ring"
- role="group"
- tabindex="-1"
- >
- <span>
- Provide an example payload from the monitoring tool you intend to integrate with. This will allow you to send an alert to an active GitLab alerting point.
- </span>
-
- <textarea
- class="gl-form-input gl-form-textarea gl-my-3 form-control is-valid"
- id="test-payload"
- placeholder="{ \\"events\\": [{ \\"application\\": \\"Name of application\\" }] }"
- style="resize: none; overflow-y: scroll;"
- wrap="soft"
- />
- <!---->
- <!---->
- <!---->
- </div>
- </fieldset>
-
- <button
- class="btn js-no-auto-disable btn-confirm btn-md gl-button"
- data-testid="send-test-alert"
- type="button"
- >
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Send
-
- </span>
- </button>
-
- <button
- class="btn gl-ml-3 js-no-auto-disable btn-default btn-md gl-button"
- type="reset"
- >
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
- Cancel and close
- </span>
- </button>
- </div>
- </transition-stub>
- <!---->
- </div>
- </div>
-</form>
-`;
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' });
});
});
});
diff --git a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
index 77fac6dd022..dd8ce838dfd 100644
--- a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
@@ -1,18 +1,18 @@
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
-import AlertsSettingsWrapper, {
- i18n,
-} from '~/alerts_settings/components/alerts_settings_wrapper.vue';
-import { typeSet } from '~/alerts_settings/constants';
+import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
+import { typeSet, i18n } from '~/alerts_settings/constants';
import createPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql';
import destroyHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '~/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql';
@@ -27,10 +27,12 @@ import {
RESET_INTEGRATION_TOKEN_ERROR,
UPDATE_INTEGRATION_ERROR,
INTEGRATION_PAYLOAD_TEST_ERROR,
+ INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
DELETE_INTEGRATION_ERROR,
} from '~/alerts_settings/utils/error_messages';
import createFlash, { FLASH_TYPES } from '~/flash';
import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
import {
createHttpVariables,
updateHttpVariables,
@@ -81,8 +83,9 @@ describe('AlertsSettingsWrapper', () => {
const findLoader = () => wrapper.findComponent(IntegrationsList).findComponent(GlLoadingIcon);
const findIntegrationsList = () => wrapper.findComponent(IntegrationsList);
const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr');
- const findAddIntegrationBtn = () => wrapper.find('[data-testid="add-integration-btn"]');
+ const findAddIntegrationBtn = () => wrapper.findByTestId('add-integration-btn');
const findAlertsSettingsForm = () => wrapper.findComponent(AlertsSettingsForm);
+ const findAlert = () => wrapper.findComponent(GlAlert);
async function destroyHttpIntegration(localWrapper) {
await jest.runOnlyPendingTimers();
@@ -94,32 +97,34 @@ describe('AlertsSettingsWrapper', () => {
}
async function awaitApolloDomMock() {
- await wrapper.vm.$nextTick(); // kick off the DOM update
+ await nextTick(); // kick off the DOM update
await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
- await wrapper.vm.$nextTick(); // kick off the DOM update for flash
+ await nextTick(); // kick off the DOM update for flash
}
const createComponent = ({ data = {}, provide = {}, loading = false } = {}) => {
- wrapper = mount(AlertsSettingsWrapper, {
- data() {
- return { ...data };
- },
- provide: {
- ...defaultAlertSettingsConfig,
- ...provide,
- },
- mocks: {
- $apollo: {
- mutate: jest.fn(),
- query: jest.fn(),
- queries: {
- integrations: {
- loading,
+ wrapper = extendedWrapper(
+ mount(AlertsSettingsWrapper, {
+ data() {
+ return { ...data };
+ },
+ provide: {
+ ...defaultAlertSettingsConfig,
+ ...provide,
+ },
+ mocks: {
+ $apollo: {
+ mutate: jest.fn(),
+ query: jest.fn(),
+ queries: {
+ integrations: {
+ loading,
+ },
},
},
},
- },
- });
+ }),
+ );
};
function createComponentWithApollo({
@@ -200,20 +205,29 @@ describe('AlertsSettingsWrapper', () => {
loading: false,
});
});
- it('calls `$apollo.mutate` with `createHttpIntegrationMutation`', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
- data: { createHttpIntegrationMutation: { integration: { id: '1' } } },
+
+ describe('Create', () => {
+ beforeEach(() => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
+ data: { httpIntegrationCreate: { integration: { id: '1' }, errors: [] } },
+ });
+ findAlertsSettingsForm().vm.$emit('create-new-integration', {
+ type: typeSet.http,
+ variables: createHttpVariables,
+ });
});
- findAlertsSettingsForm().vm.$emit('create-new-integration', {
- type: typeSet.http,
- variables: createHttpVariables,
+
+ it('calls `$apollo.mutate` with `createHttpIntegrationMutation`', () => {
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: createHttpIntegrationMutation,
+ update: expect.anything(),
+ variables: createHttpVariables,
+ });
});
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: createHttpIntegrationMutation,
- update: expect.anything(),
- variables: createHttpVariables,
+ it('shows success alert', () => {
+ expect(findAlert().exists()).toBe(true);
});
});
@@ -334,13 +348,29 @@ describe('AlertsSettingsWrapper', () => {
expect(createFlash).toHaveBeenCalledWith({ message: UPDATE_INTEGRATION_ERROR });
});
- it('shows an error alert when integration test payload fails ', async () => {
- const mock = new AxiosMockAdapter(axios);
- mock.onPost(/(.*)/).replyOnce(403);
- return wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' }).then(() => {
+ describe('Test alert failure', () => {
+ let mock;
+ beforeEach(() => {
+ mock = new AxiosMockAdapter(axios);
+ });
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('shows an error alert when integration test payload is invalid ', async () => {
+ mock.onPost(/(.*)/).replyOnce(httpStatusCodes.UNPROCESSABLE_ENTITY);
+ await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
expect(createFlash).toHaveBeenCalledTimes(1);
- mock.restore();
+ });
+
+ it('shows an error alert when integration is not activated ', async () => {
+ mock.onPost(/(.*)/).replyOnce(httpStatusCodes.FORBIDDEN);
+ await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
+ expect(createFlash).toHaveBeenCalledWith({
+ message: INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
+ });
+ expect(createFlash).toHaveBeenCalledTimes(1);
});
});
@@ -354,7 +384,7 @@ describe('AlertsSettingsWrapper', () => {
loading: false,
});
- jest.spyOn(wrapper.vm.$apollo, 'mutate');
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValueOnce({});
findIntegrationsList().vm.$emit('edit-integration', updateHttpVariables);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updateCurrentHttpIntegrationMutation,
@@ -372,7 +402,7 @@ describe('AlertsSettingsWrapper', () => {
loading: false,
});
- jest.spyOn(wrapper.vm.$apollo, 'mutate');
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
findIntegrationsList().vm.$emit('edit-integration', updatePrometheusVariables);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updateCurrentPrometheusIntegrationMutation,
@@ -414,7 +444,7 @@ describe('AlertsSettingsWrapper', () => {
createComponentWithApollo();
await jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findIntegrations()).toHaveLength(4);
});
@@ -426,7 +456,7 @@ describe('AlertsSettingsWrapper', () => {
expect(destroyIntegrationHandler).toHaveBeenCalled();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findIntegrations()).toHaveLength(3);
});