From 7021455bd1ed7b125c55eb1b33c5a01f2bc55ee0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 17 Nov 2022 11:33:21 +0000 Subject: Add latest changes from gitlab-org/gitlab@15-6-stable-ee --- .../__snapshots__/push_events_spec.js.snap | 453 +++++++++++++++++++++ .../webhooks/components/form_url_app_spec.js | 125 +++++- .../webhooks/components/form_url_mask_item_spec.js | 78 +++- .../webhooks/components/push_events_spec.js | 117 ++++++ 4 files changed, 747 insertions(+), 26 deletions(-) create mode 100644 spec/frontend/webhooks/components/__snapshots__/push_events_spec.js.snap create mode 100644 spec/frontend/webhooks/components/push_events_spec.js (limited to 'spec/frontend/webhooks') diff --git a/spec/frontend/webhooks/components/__snapshots__/push_events_spec.js.snap b/spec/frontend/webhooks/components/__snapshots__/push_events_spec.js.snap new file mode 100644 index 00000000000..3dbff024a6b --- /dev/null +++ b/spec/frontend/webhooks/components/__snapshots__/push_events_spec.js.snap @@ -0,0 +1,453 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Webhook push events form editor component Different push events rules when editing existing hook with "all_branches" strategy selected 1`] = ` + + +
+ All branches +
+
+ + +
+ + Wildcard pattern + +
+
+ +
+ +
+ + + + +
+ + Regular expression + +
+
+ +
+ +
+ + +
+`; + +exports[`Webhook push events form editor component Different push events rules when editing existing hook with "regex" strategy selected 1`] = ` + + +
+ All branches +
+
+ + +
+ + Wildcard pattern + +
+
+ +
+ +
+ + + + +
+ + Regular expression + +
+
+ +
+ +
+ +

+ +

+
+`; + +exports[`Webhook push events form editor component Different push events rules when editing existing hook with "wildcard" strategy selected 1`] = ` + + +
+ All branches +
+
+ + +
+ + Wildcard pattern + +
+
+ +
+ +
+ +

+ +

+ + +
+ + Regular expression + +
+
+ +
+ +
+ + +
+`; + +exports[`Webhook push events form editor component Different push events rules when editing new hook all_branches should be selected by default 1`] = ` + + +
+ All branches +
+
+ + +
+ + Wildcard pattern + +
+
+ +
+ +
+ + + + +
+ + Regular expression + +
+
+ +
+ +
+ + +
+`; + +exports[`Webhook push events form editor component Different push events rules when editing new hook should be able to set regex rule 1`] = ` + + +
+ All branches +
+
+ + +
+ + Wildcard pattern + +
+
+ +
+ +
+ + + + +
+ + Regular expression + +
+
+ +
+ +
+ +

+ +

+
+`; + +exports[`Webhook push events form editor component Different push events rules when editing new hook should be able to set wildcard rule 1`] = ` + + +
+ All branches +
+
+ + +
+ + Wildcard pattern + +
+
+ +
+ +
+ +

+ +

+ + +
+ + Regular expression + +
+
+ +
+ +
+ + +
+`; diff --git a/spec/frontend/webhooks/components/form_url_app_spec.js b/spec/frontend/webhooks/components/form_url_app_spec.js index 16e0a3f549e..45a39d2dd58 100644 --- a/spec/frontend/webhooks/components/form_url_app_spec.js +++ b/spec/frontend/webhooks/components/form_url_app_spec.js @@ -1,10 +1,14 @@ import { nextTick } from 'vue'; -import { GlFormRadio, GlFormRadioGroup, GlLink } from '@gitlab/ui'; +import { GlFormGroup, GlFormRadio, GlFormRadioGroup, GlLink } from '@gitlab/ui'; +import { scrollToElement } from '~/lib/utils/common_utils'; import FormUrlApp from '~/webhooks/components/form_url_app.vue'; import FormUrlMaskItem from '~/webhooks/components/form_url_mask_item.vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; + +jest.mock('~/lib/utils/common_utils'); describe('FormUrlApp', () => { let wrapper; @@ -26,8 +30,11 @@ describe('FormUrlApp', () => { const findAllUrlMaskItems = () => wrapper.findAllComponents(FormUrlMaskItem); const findAddItem = () => wrapper.findComponent(GlLink); const findFormUrl = () => wrapper.findByTestId('form-url'); + const findFormUrlGroup = () => wrapper.findAllComponents(GlFormGroup).at(0); const findFormUrlPreview = () => wrapper.findByTestId('form-url-preview'); const findUrlMaskSection = () => wrapper.findByTestId('url-mask-section'); + const findFormEl = () => document.querySelector('.js-webhook-form'); + const submitForm = () => findFormEl().dispatchEvent(new Event('submit')); describe('template', () => { it('renders radio buttons for URL masking', () => { @@ -60,8 +67,10 @@ describe('FormUrlApp', () => { expect(findAllUrlMaskItems()).toHaveLength(1); const firstItem = findAllUrlMaskItems().at(0); - expect(firstItem.props('itemKey')).toBeNull(); - expect(firstItem.props('itemValue')).toBeNull(); + expect(firstItem.props()).toMatchObject({ + itemKey: null, + itemValue: null, + }); }); }); @@ -90,12 +99,18 @@ describe('FormUrlApp', () => { expect(findAllUrlMaskItems()).toHaveLength(2); const firstItem = findAllUrlMaskItems().at(0); - expect(firstItem.props('itemKey')).toBe(mockItem1.key); - expect(firstItem.props('itemValue')).toBe(mockItem1.value); + expect(firstItem.props()).toMatchObject({ + itemKey: mockItem1.key, + itemValue: mockItem1.value, + isEditing: true, + }); const secondItem = findAllUrlMaskItems().at(1); - expect(secondItem.props('itemKey')).toBe(mockItem2.key); - expect(secondItem.props('itemValue')).toBe(mockItem2.value); + expect(secondItem.props()).toMatchObject({ + itemKey: mockItem2.key, + itemValue: mockItem2.value, + isEditing: true, + }); }); describe('on mask item input', () => { @@ -106,8 +121,10 @@ describe('FormUrlApp', () => { firstItem.vm.$emit('input', mockInput); await nextTick(); - expect(firstItem.props('itemKey')).toBe(mockInput.key); - expect(firstItem.props('itemValue')).toBe(mockInput.value); + expect(firstItem.props()).toMatchObject({ + itemKey: mockInput.key, + itemValue: mockInput.value, + }); }); }); @@ -119,8 +136,10 @@ describe('FormUrlApp', () => { expect(findAllUrlMaskItems()).toHaveLength(3); const lastItem = findAllUrlMaskItems().at(-1); - expect(lastItem.props('itemKey')).toBeNull(); - expect(lastItem.props('itemValue')).toBeNull(); + expect(lastItem.props()).toMatchObject({ + itemKey: null, + itemValue: null, + }); }); }); @@ -133,8 +152,88 @@ describe('FormUrlApp', () => { expect(findAllUrlMaskItems()).toHaveLength(1); const newFirstItem = findAllUrlMaskItems().at(0); - expect(newFirstItem.props('itemKey')).toBe(mockItem2.key); - expect(newFirstItem.props('itemValue')).toBe(mockItem2.value); + expect(newFirstItem.props()).toMatchObject({ + itemKey: mockItem2.key, + itemValue: mockItem2.value, + }); + }); + }); + }); + + describe('validations', () => { + const inputRequiredText = FormUrlApp.i18n.inputRequired; + + beforeEach(() => { + setHTMLFixture('
'); + }); + + afterEach(() => { + resetHTMLFixture(); + }); + + it.each` + url | state | scrollToElementCalls + ${null} | ${undefined} | ${1} + ${''} | ${undefined} | ${1} + ${'https://example.com/'} | ${'true'} | ${0} + `('when URL is `$url`, state is `$state`', async ({ url, state, scrollToElementCalls }) => { + createComponent({ + props: { initialUrl: url }, + }); + + submitForm(); + await nextTick(); + + expect(findFormUrlGroup().attributes('state')).toBe(state); + expect(scrollToElement).toHaveBeenCalledTimes(scrollToElementCalls); + expect(findFormUrlGroup().attributes('invalid-feedback')).toBe(inputRequiredText); + }); + + it.each` + key | value | keyInvalidFeedback | valueInvalidFeedback | scrollToElementCalls + ${null} | ${null} | ${inputRequiredText} | ${inputRequiredText} | ${1} + ${null} | ${'random'} | ${inputRequiredText} | ${FormUrlApp.i18n.valuePartOfUrl} | ${1} + ${null} | ${'secret'} | ${inputRequiredText} | ${null} | ${1} + ${'key'} | ${null} | ${null} | ${inputRequiredText} | ${1} + ${'key'} | ${'secret'} | ${null} | ${null} | ${0} + `( + 'when key is `$key` and value is `$value`', + async ({ key, value, keyInvalidFeedback, valueInvalidFeedback, scrollToElementCalls }) => { + createComponent({ + props: { initialUrl: 'http://example.com?password=secret' }, + }); + findRadioGroup().vm.$emit('input', true); + await nextTick(); + + const maskItem = findAllUrlMaskItems().at(0); + const mockInput = { index: 0, key, value }; + maskItem.vm.$emit('input', mockInput); + + submitForm(); + await nextTick(); + + expect(maskItem.props('keyInvalidFeedback')).toBe(keyInvalidFeedback); + expect(maskItem.props('valueInvalidFeedback')).toBe(valueInvalidFeedback); + expect(scrollToElement).toHaveBeenCalledTimes(scrollToElementCalls); + }, + ); + + describe('when initialUrlVariables is passed', () => { + it('does not validate empty values', async () => { + const initialUrlVariables = [{ key: 'key' }]; + + createComponent({ + props: { initialUrl: 'url', initialUrlVariables }, + }); + + submitForm(); + await nextTick(); + + const maskItem = findAllUrlMaskItems().at(0); + + expect(maskItem.props('keyInvalidFeedback')).toBeNull(); + expect(maskItem.props('valueInvalidFeedback')).toBeNull(); + expect(scrollToElement).not.toHaveBeenCalled(); }); }); }); diff --git a/spec/frontend/webhooks/components/form_url_mask_item_spec.js b/spec/frontend/webhooks/components/form_url_mask_item_spec.js index ab028ef2997..06c743749a6 100644 --- a/spec/frontend/webhooks/components/form_url_mask_item_spec.js +++ b/spec/frontend/webhooks/components/form_url_mask_item_spec.js @@ -14,6 +14,7 @@ describe('FormUrlMaskItem', () => { const mockKey = 'key'; const mockValue = 'value'; const mockInput = 'input'; + const mockFeedback = 'feedback'; const createComponent = ({ props } = {}) => { wrapper = shallowMountExtended(FormUrlMaskItem, { @@ -21,29 +22,80 @@ describe('FormUrlMaskItem', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - const findMaskItemKey = () => wrapper.findByTestId('mask-item-key'); const findMaskItemValue = () => wrapper.findByTestId('mask-item-value'); const findRemoveButton = () => wrapper.findComponent(GlButton); describe('template', () => { it('renders input for key and value', () => { - createComponent(); + createComponent({ props: { itemKey: mockKey, itemValue: mockValue } }); const keyInput = findMaskItemKey(); - expect(keyInput.attributes('label')).toBe(FormUrlMaskItem.i18n.keyLabel); - expect(keyInput.findComponent(GlFormInput).attributes('name')).toBe( - 'hook[url_variables][][key]', - ); + expect(keyInput.attributes()).toMatchObject({ + label: FormUrlMaskItem.i18n.keyLabel, + state: 'true', + }); + expect(keyInput.findComponent(GlFormInput).attributes()).toMatchObject({ + name: 'hook[url_variables][][key]', + value: mockKey, + }); const valueInput = findMaskItemValue(); - expect(valueInput.attributes('label')).toBe(FormUrlMaskItem.i18n.valueLabel); - expect(valueInput.findComponent(GlFormInput).attributes('name')).toBe( - 'hook[url_variables][][value]', - ); + expect(valueInput.attributes()).toMatchObject({ + label: FormUrlMaskItem.i18n.valueLabel, + state: 'true', + }); + expect(valueInput.findComponent(GlFormInput).attributes()).toMatchObject({ + name: 'hook[url_variables][][value]', + value: mockValue, + }); + }); + + describe('when isEditing is true', () => { + beforeEach(() => { + createComponent({ props: { isEditing: true } }); + }); + + it('renders disabled key and value', () => { + expect(findMaskItemKey().findComponent(GlFormInput).attributes('disabled')).toBe('true'); + expect(findMaskItemValue().findComponent(GlFormInput).attributes('disabled')).toBe('true'); + }); + + it('renders disabled remove button', () => { + expect(findRemoveButton().attributes('disabled')).toBe('true'); + }); + + it('displays ************ as input value', () => { + expect(findMaskItemValue().findComponent(GlFormInput).attributes('value')).toBe( + '************', + ); + }); + }); + + describe('when keyInvalidFeedback is passed', () => { + beforeEach(() => { + createComponent({ + props: { keyInvalidFeedback: mockFeedback }, + }); + }); + + it('sets validation message on key', () => { + expect(findMaskItemKey().attributes('invalid-feedback')).toBe(mockFeedback); + expect(findMaskItemKey().attributes('state')).toBeUndefined(); + }); + }); + + describe('when valueInvalidFeedback is passed', () => { + beforeEach(() => { + createComponent({ + props: { valueInvalidFeedback: mockFeedback }, + }); + }); + + it('sets validation message on value', () => { + expect(findMaskItemValue().attributes('invalid-feedback')).toBe(mockFeedback); + expect(findMaskItemValue().attributes('state')).toBeUndefined(); + }); }); describe('on key input', () => { diff --git a/spec/frontend/webhooks/components/push_events_spec.js b/spec/frontend/webhooks/components/push_events_spec.js new file mode 100644 index 00000000000..ccb61c4049a --- /dev/null +++ b/spec/frontend/webhooks/components/push_events_spec.js @@ -0,0 +1,117 @@ +import { nextTick } from 'vue'; +import { GlFormCheckbox, GlFormRadioGroup } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import PushEvents from '~/webhooks/components/push_events.vue'; + +describe('Webhook push events form editor component', () => { + let wrapper; + + const findPushEventsCheckBox = (w = wrapper) => w.findComponent(GlFormCheckbox); + const findPushEventsIndicator = (w = wrapper) => w.find('input[name="hook[push_events]"]'); + const findPushEventRulesGroup = (w = wrapper) => w.findComponent(GlFormRadioGroup); + const getPushEventsRuleValue = (w = wrapper) => findPushEventRulesGroup(w).vm.$attrs.checked; + const findWildcardRuleInput = (w = wrapper) => w.findByTestId('webhook_branch_filter_field'); + const findRegexRuleInput = (w = wrapper) => w.findByTestId('webhook_branch_filter_field'); + + const createComponent = (provides) => + shallowMountExtended(PushEvents, { + provide: { + isNewHook: true, + pushEvents: false, + strategy: 'wildcard', + pushEventsBranchFilter: '', + ...provides, + }, + }); + + describe('Renders push events checkbox', () => { + it('when it is a new hook', async () => { + wrapper = createComponent({ + isNewHook: true, + }); + await nextTick(); + + const checkbox = findPushEventsCheckBox(); + expect(checkbox.exists()).toBe(true); + expect(findPushEventRulesGroup().exists()).toBe(false); + expect(findPushEventsIndicator().attributes('value')).toBe('false'); + }); + + it('when it is not a new hook and push events is enabled', async () => { + wrapper = createComponent({ + isNewHook: false, + pushEvents: true, + }); + await nextTick(); + + expect(findPushEventsCheckBox().exists()).toBe(true); + expect(findPushEventRulesGroup().exists()).toBe(true); + expect(findPushEventsIndicator().attributes('value')).toBe('true'); + }); + }); + + describe('Different push events rules', () => { + describe('when editing new hook', () => { + beforeEach(async () => { + wrapper = createComponent({ + isNewHook: true, + }); + await nextTick(); + await findPushEventsCheckBox().vm.$emit('input', true); + await nextTick(); + }); + + it('all_branches should be selected by default', async () => { + expect(findPushEventRulesGroup().element).toMatchSnapshot(); + }); + + it('should be able to set wildcard rule', async () => { + expect(getPushEventsRuleValue()).toBe('all_branches'); + expect(findWildcardRuleInput().exists()).toBe(false); + expect(findRegexRuleInput().exists()).toBe(false); + + await findPushEventRulesGroup(wrapper).vm.$emit('input', 'wildcard'); + expect(findWildcardRuleInput().exists()).toBe(true); + expect(findPushEventRulesGroup().element).toMatchSnapshot(); + + const testVal = 'test-val'; + findWildcardRuleInput().vm.$emit('input', testVal); + await nextTick(); + expect(findWildcardRuleInput().attributes('value')).toBe(testVal); + }); + + it('should be able to set regex rule', async () => { + expect(getPushEventsRuleValue()).toBe('all_branches'); + expect(findRegexRuleInput().exists()).toBe(false); + expect(findWildcardRuleInput().exists()).toBe(false); + + await findPushEventRulesGroup(wrapper).vm.$emit('input', 'regex'); + expect(findRegexRuleInput().exists()).toBe(true); + expect(findPushEventRulesGroup().element).toMatchSnapshot(); + + const testVal = 'test-val'; + findRegexRuleInput().vm.$emit('input', testVal); + await nextTick(); + expect(findRegexRuleInput().attributes('value')).toBe(testVal); + }); + }); + + describe('when editing existing hook', () => { + it.each(['all_branches', 'wildcard', 'regex'])( + 'with "%s" strategy selected', + async (strategy) => { + wrapper = createComponent({ + isNewHook: false, + pushEvents: true, + pushEventsBranchFilter: 'foo', + strategy, + }); + await nextTick(); + + expect(findPushEventsIndicator().attributes('value')).toBe('true'); + expect(findPushEventRulesGroup().element).toMatchSnapshot(); + }, + ); + }); + }); +}); -- cgit v1.2.3