Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/branches/components/__snapshots__/delete_merged_branches_spec.js.snap139
-rw-r--r--spec/frontend/branches/components/delete_merged_branches_spec.js143
-rw-r--r--spec/frontend/branches/mock_data.js7
-rw-r--r--spec/frontend/ci_variable_list/components/legacy_ci_environments_dropdown_spec.js119
-rw-r--r--spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js323
-rw-r--r--spec/frontend/ci_variable_list/components/legacy_ci_variable_settings_spec.js38
-rw-r--r--spec/frontend/ci_variable_list/components/legacy_ci_variable_table_spec.js86
-rw-r--r--spec/frontend/ci_variable_list/store/actions_spec.js319
-rw-r--r--spec/frontend/ci_variable_list/store/getters_spec.js21
-rw-r--r--spec/frontend/ci_variable_list/store/mutations_spec.js136
-rw-r--r--spec/frontend/ci_variable_list/store/utils_spec.js49
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/forwarding_settings_spec.js78
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js17
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js14
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js280
-rw-r--r--spec/frontend/packages_and_registries/settings/group/mock_data.js75
16 files changed, 735 insertions, 1109 deletions
diff --git a/spec/frontend/branches/components/__snapshots__/delete_merged_branches_spec.js.snap b/spec/frontend/branches/components/__snapshots__/delete_merged_branches_spec.js.snap
new file mode 100644
index 00000000000..6aab3b51806
--- /dev/null
+++ b/spec/frontend/branches/components/__snapshots__/delete_merged_branches_spec.js.snap
@@ -0,0 +1,139 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Delete merged branches component Delete merged branches confirmation modal matches snapshot 1`] = `
+<div>
+ <b-button-stub
+ class="gl-mr-3 gl-button btn-danger-secondary"
+ data-qa-selector="delete_merged_branches_button"
+ size="md"
+ tag="button"
+ type="button"
+ variant="danger"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+ Delete merged branches
+
+ </span>
+ </b-button-stub>
+
+ <div>
+ <form
+ action="/namespace/project/-/merged_branches"
+ method="post"
+ >
+ <p>
+ You are about to
+ <strong>
+ delete all branches
+ </strong>
+ that were merged into
+ <code>
+ master
+ </code>
+ .
+ </p>
+
+ <p>
+
+ This may include merged branches that are not visible on the current screen.
+
+ </p>
+
+ <p>
+
+ A branch won't be deleted if it is protected or associated with an open merge request.
+
+ </p>
+
+ <p>
+ This bulk action is
+ <strong>
+ permanent and cannot be undone or recovered
+ </strong>
+ .
+ </p>
+
+ <p>
+ Plese type the following to confirm:
+ <code>
+ delete
+ </code>
+ .
+ <b-form-input-stub
+ aria-labelledby="input-label"
+ autocomplete="off"
+ class="gl-form-input gl-mt-2 gl-form-input-sm"
+ data-qa-selector="delete_merged_branches_input"
+ debounce="0"
+ formatter="[Function]"
+ type="text"
+ value=""
+ />
+ </p>
+
+ <input
+ name="_method"
+ type="hidden"
+ value="delete"
+ />
+
+ <input
+ name="authenticity_token"
+ type="hidden"
+ value="mock-csrf-token"
+ />
+ </form>
+ <div
+ class="gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-m-0 gl-mr-3"
+ >
+ <b-button-stub
+ class="gl-button"
+ data-testid="delete-merged-branches-cancel-button"
+ size="md"
+ tag="button"
+ type="button"
+ variant="default"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+
+ Cancel
+
+ </span>
+ </b-button-stub>
+
+ <b-button-stub
+ class="gl-button"
+ data-qa-selector="delete_merged_branches_confirmation_button"
+ data-testid="delete-merged-branches-confirmation-button"
+ disabled="true"
+ size="md"
+ tag="button"
+ type="button"
+ variant="danger"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+ Delete merged branches
+ </span>
+ </b-button-stub>
+ </div>
+ </div>
+</div>
+`;
diff --git a/spec/frontend/branches/components/delete_merged_branches_spec.js b/spec/frontend/branches/components/delete_merged_branches_spec.js
new file mode 100644
index 00000000000..4f1e772f4a4
--- /dev/null
+++ b/spec/frontend/branches/components/delete_merged_branches_spec.js
@@ -0,0 +1,143 @@
+import { GlButton, GlModal, GlFormInput, GlSprintf } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { stubComponent } from 'helpers/stub_component';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import DeleteMergedBranches, { i18n } from '~/branches/components/delete_merged_branches.vue';
+import { formPath, propsDataMock } from '../mock_data';
+
+jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
+
+let wrapper;
+
+const stubsData = {
+ GlModal: stubComponent(GlModal, {
+ template:
+ '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
+ }),
+ GlButton,
+ GlFormInput,
+ GlSprintf,
+};
+
+const createComponent = (mountFn = shallowMountExtended, stubs = {}) => {
+ wrapper = mountFn(DeleteMergedBranches, {
+ propsData: {
+ ...propsDataMock,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ stubs,
+ });
+};
+
+const findDeleteButton = () => wrapper.findComponent(GlButton);
+const findModal = () => wrapper.findComponent(GlModal);
+const findConfirmationButton = () =>
+ wrapper.findByTestId('delete-merged-branches-confirmation-button');
+const findCancelButton = () => wrapper.findByTestId('delete-merged-branches-cancel-button');
+const findFormInput = () => wrapper.findComponent(GlFormInput);
+const findForm = () => wrapper.find('form');
+const submitFormSpy = () => jest.spyOn(wrapper.vm.$refs.form, 'submit');
+
+describe('Delete merged branches component', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ describe('Delete merged branches button', () => {
+ it('has correct attributes, text and tooltip', () => {
+ expect(findDeleteButton().attributes()).toMatchObject({
+ category: 'secondary',
+ variant: 'danger',
+ });
+
+ expect(findDeleteButton().text()).toBe(i18n.deleteButtonText);
+ });
+
+ it('displays a tooltip', () => {
+ const tooltip = getBinding(findDeleteButton().element, 'gl-tooltip');
+
+ expect(tooltip).toBeDefined();
+ expect(tooltip.value).toBe(wrapper.vm.buttonTooltipText);
+ });
+
+ it('opens modal when clicked', () => {
+ createComponent(mount);
+ jest.spyOn(wrapper.vm.$refs.modal, 'show');
+ findDeleteButton().trigger('click');
+
+ expect(wrapper.vm.$refs.modal.show).toHaveBeenCalled();
+ });
+ });
+
+ describe('Delete merged branches confirmation modal', () => {
+ beforeEach(() => {
+ createComponent(shallowMountExtended, stubsData);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders correct modal title and text', () => {
+ const modalText = findModal().text();
+ expect(findModal().props('title')).toBe(i18n.modalTitle);
+ expect(modalText).toContain(i18n.notVisibleBranchesWarning);
+ expect(modalText).toContain(i18n.protectedBranchWarning);
+ });
+
+ it('renders confirm and cancel buttons with correct text', () => {
+ expect(findConfirmationButton().text()).toContain(i18n.deleteButtonText);
+ expect(findCancelButton().text()).toContain(i18n.cancelButtonText);
+ });
+
+ it('renders form with correct attributes and hiden inputs', () => {
+ const form = findForm();
+ expect(form.attributes()).toEqual({
+ action: formPath,
+ method: 'post',
+ });
+ expect(form.find('input[name="_method"]').attributes('value')).toBe('delete');
+ expect(form.find('input[name="authenticity_token"]').attributes('value')).toBe(
+ 'mock-csrf-token',
+ );
+ });
+
+ it('matches snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('has a disabled confirm button by default', () => {
+ expect(findConfirmationButton().props('disabled')).toBe(true);
+ });
+
+ it('keeps disabled state when wrong input is provided', async () => {
+ findFormInput().vm.$emit('input', 'hello');
+ await waitForPromises();
+ expect(findConfirmationButton().props('disabled')).toBe(true);
+ findConfirmationButton().trigger('click');
+
+ expect(submitFormSpy()).not.toHaveBeenCalled();
+ findFormInput().trigger('keyup.enter');
+
+ expect(submitFormSpy()).not.toHaveBeenCalled();
+ });
+
+ it('submits form when correct amount is provided and the confirm button is clicked', async () => {
+ findFormInput().vm.$emit('input', 'delete');
+ await waitForPromises();
+ expect(findDeleteButton().props('disabled')).not.toBe(true);
+ findConfirmationButton().trigger('click');
+ expect(submitFormSpy()).toHaveBeenCalled();
+ });
+
+ it('calls hide on the modal when cancel button is clicked', () => {
+ const closeModalSpy = jest.spyOn(wrapper.vm.$refs.modal, 'hide');
+ findCancelButton().trigger('click');
+ expect(closeModalSpy).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/branches/mock_data.js b/spec/frontend/branches/mock_data.js
new file mode 100644
index 00000000000..9e8839d8ce9
--- /dev/null
+++ b/spec/frontend/branches/mock_data.js
@@ -0,0 +1,7 @@
+export const formPath = '/namespace/project/-/merged_branches';
+const defaultBranch = 'master';
+
+export const propsDataMock = {
+ formPath,
+ defaultBranch,
+};
diff --git a/spec/frontend/ci_variable_list/components/legacy_ci_environments_dropdown_spec.js b/spec/frontend/ci_variable_list/components/legacy_ci_environments_dropdown_spec.js
deleted file mode 100644
index b3e23ba4201..00000000000
--- a/spec/frontend/ci_variable_list/components/legacy_ci_environments_dropdown_spec.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
-import Vuex from 'vuex';
-import LegacyCiEnvironmentsDropdown from '~/ci_variable_list/components/legacy_ci_environments_dropdown.vue';
-
-Vue.use(Vuex);
-
-describe('Ci environments dropdown', () => {
- let wrapper;
- let store;
-
- const enterSearchTerm = (value) =>
- wrapper.find('[data-testid="ci-environment-search"]').setValue(value);
-
- const createComponent = (term) => {
- store = new Vuex.Store({
- getters: {
- joinedEnvironments: () => ['dev', 'prod', 'staging'],
- },
- });
-
- wrapper = mount(LegacyCiEnvironmentsDropdown, {
- store,
- propsData: {
- value: term,
- },
- });
- enterSearchTerm(term);
- };
-
- const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
- const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index);
- const findActiveIconByIndex = (index) => findDropdownItemByIndex(index).findComponent(GlIcon);
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('No environments found', () => {
- beforeEach(() => {
- createComponent('stable');
- });
-
- it('renders create button with search term if environments do not contain search term', () => {
- expect(findAllDropdownItems()).toHaveLength(2);
- expect(findDropdownItemByIndex(1).text()).toBe('Create wildcard: stable');
- });
-
- it('renders empty results message', () => {
- expect(findDropdownItemByIndex(0).text()).toBe('No matching results');
- });
- });
-
- describe('Search term is empty', () => {
- beforeEach(() => {
- createComponent('');
- });
-
- it('renders all environments when search term is empty', () => {
- expect(findAllDropdownItems()).toHaveLength(3);
- expect(findDropdownItemByIndex(0).text()).toBe('dev');
- expect(findDropdownItemByIndex(1).text()).toBe('prod');
- expect(findDropdownItemByIndex(2).text()).toBe('staging');
- });
-
- it('should not display active checkmark on the inactive stage', () => {
- expect(findActiveIconByIndex(0).classes('gl-visibility-hidden')).toBe(true);
- });
- });
-
- describe('Environments found', () => {
- beforeEach(async () => {
- createComponent('prod');
- await nextTick();
- });
-
- it('renders only the environment searched for', () => {
- expect(findAllDropdownItems()).toHaveLength(1);
- expect(findDropdownItemByIndex(0).text()).toBe('prod');
- });
-
- it('should not display create button', () => {
- const environments = findAllDropdownItems().filter((env) => env.text().startsWith('Create'));
- expect(environments).toHaveLength(0);
- expect(findAllDropdownItems()).toHaveLength(1);
- });
-
- it('should not display empty results message', () => {
- expect(wrapper.findComponent({ ref: 'noMatchingResults' }).exists()).toBe(false);
- });
-
- it('should display active checkmark if active', () => {
- expect(findActiveIconByIndex(0).classes('gl-visibility-hidden')).toBe(false);
- });
-
- it('should clear the search term when showing the dropdown', () => {
- wrapper.findComponent(GlDropdown).trigger('click');
-
- expect(wrapper.find('[data-testid="ci-environment-search"]').text()).toBe('');
- });
-
- describe('Custom events', () => {
- it('should emit selectEnvironment if an environment is clicked', () => {
- findDropdownItemByIndex(0).vm.$emit('click');
- expect(wrapper.emitted('selectEnvironment')).toEqual([['prod']]);
- });
-
- it('should emit createClicked if an environment is clicked', async () => {
- createComponent('newscope');
-
- await nextTick();
- findDropdownItemByIndex(1).vm.$emit('click');
- expect(wrapper.emitted('createClicked')).toEqual([['newscope']]);
- });
- });
- });
-});
diff --git a/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js
deleted file mode 100644
index b607232907b..00000000000
--- a/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js
+++ /dev/null
@@ -1,323 +0,0 @@
-import { GlButton, GlFormInput } from '@gitlab/ui';
-import { shallowMount, mount } from '@vue/test-utils';
-import Vue from 'vue';
-import Vuex from 'vuex';
-import { mockTracking } from 'helpers/tracking_helper';
-import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
-import LegacyCiVariableModal from '~/ci_variable_list/components/legacy_ci_variable_modal.vue';
-import {
- AWS_ACCESS_KEY_ID,
- EVENT_LABEL,
- EVENT_ACTION,
- ENVIRONMENT_SCOPE_LINK_TITLE,
-} from '~/ci_variable_list/constants';
-import createStore from '~/ci_variable_list/store';
-import mockData from '../services/mock_data';
-import ModalStub from '../stubs';
-
-Vue.use(Vuex);
-
-describe('Ci variable modal', () => {
- let wrapper;
- let store;
- let trackingSpy;
-
- const maskableRegex = '^[a-zA-Z0-9_+=/@:.~-]{8,}$';
-
- const createComponent = (method, options = {}) => {
- store = createStore({
- maskableRegex,
- isGroup: options.isGroup,
- environmentScopeLink: '/help/environments',
- });
- wrapper = method(LegacyCiVariableModal, {
- attachTo: document.body,
- stubs: {
- GlModal: ModalStub,
- },
- store,
- ...options,
- });
- };
-
- const findCiEnvironmentsDropdown = () => wrapper.findComponent(CiEnvironmentsDropdown);
- const findModal = () => wrapper.findComponent(ModalStub);
- const findAddorUpdateButton = () => findModal().find('[data-testid="ciUpdateOrAddVariableBtn"]');
- const deleteVariableButton = () =>
- findModal()
- .findAllComponents(GlButton)
- .wrappers.find((button) => button.props('variant') === 'danger');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('Basic interactions', () => {
- beforeEach(() => {
- createComponent(shallowMount);
- });
-
- it('button is disabled when no key/value pair are present', () => {
- expect(findAddorUpdateButton().attributes('disabled')).toBe('true');
- });
- });
-
- describe('Adding a new variable', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- createComponent(shallowMount);
- jest.spyOn(store, 'dispatch').mockImplementation();
- store.state.variable = variable;
- });
-
- it('button is enabled when key/value pair are present', () => {
- expect(findAddorUpdateButton().attributes('disabled')).toBeUndefined();
- });
-
- it('Add variable button dispatches addVariable action', () => {
- findAddorUpdateButton().vm.$emit('click');
- expect(store.dispatch).toHaveBeenCalledWith('addVariable');
- });
-
- it('Clears the modal state once modal is hidden', () => {
- findModal().vm.$emit('hidden');
- expect(store.dispatch).toHaveBeenCalledWith('clearModal');
- });
-
- it('should dispatch setVariableProtected when admin settings are configured to protect variables', () => {
- store.state.isProtectedByDefault = true;
- findModal().vm.$emit('shown');
-
- expect(store.dispatch).toHaveBeenCalledWith('setVariableProtected');
- });
- });
-
- describe('Adding a new non-AWS variable', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const invalidKeyVariable = {
- ...variable,
- key: 'key',
- value: 'value',
- secret_value: 'secret_value',
- };
- createComponent(mount);
- store.state.variable = invalidKeyVariable;
- });
-
- it('does not show AWS guidance tip', () => {
- const tip = wrapper.find(`div[data-testid='aws-guidance-tip']`);
- expect(tip.exists()).toBe(true);
- expect(tip.isVisible()).toBe(false);
- });
- });
-
- describe('Adding a new AWS variable', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const invalidKeyVariable = {
- ...variable,
- key: AWS_ACCESS_KEY_ID,
- value: 'AKIAIOSFODNN7EXAMPLEjdhy',
- secret_value: 'AKIAIOSFODNN7EXAMPLEjdhy',
- };
- createComponent(mount);
- store.state.variable = invalidKeyVariable;
- });
-
- it('shows AWS guidance tip', () => {
- const tip = wrapper.find(`[data-testid='aws-guidance-tip']`);
- expect(tip.exists()).toBe(true);
- expect(tip.isVisible()).toBe(true);
- });
- });
-
- describe.each`
- value | secret | rendered
- ${'value'} | ${'secret_value'} | ${false}
- ${'dollar$ign'} | ${'dollar$ign'} | ${true}
- `('Adding a new variable', ({ value, secret, rendered }) => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const invalidKeyVariable = {
- ...variable,
- key: 'key',
- value,
- secret_value: secret,
- };
- createComponent(mount);
- store.state.variable = invalidKeyVariable;
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- it(`${rendered ? 'renders' : 'does not render'} the variable reference warning`, () => {
- const warning = wrapper.find(`[data-testid='contains-variable-reference']`);
- expect(warning.exists()).toBe(rendered);
- });
- });
-
- describe('Editing a variable', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- createComponent(shallowMount);
- jest.spyOn(store, 'dispatch').mockImplementation();
- store.state.variableBeingEdited = variable;
- });
-
- it('button text is Update variable when updating', () => {
- expect(findAddorUpdateButton().text()).toBe('Update variable');
- });
-
- it('Update variable button dispatches updateVariable with correct variable', () => {
- findAddorUpdateButton().vm.$emit('click');
- expect(store.dispatch).toHaveBeenCalledWith('updateVariable');
- });
-
- it('Resets the editing state once modal is hidden', () => {
- findModal().vm.$emit('hidden');
- expect(store.dispatch).toHaveBeenCalledWith('resetEditing');
- });
-
- it('dispatches deleteVariable with correct variable to delete', () => {
- deleteVariableButton().vm.$emit('click');
- expect(store.dispatch).toHaveBeenCalledWith('deleteVariable');
- });
- });
-
- describe('Environment scope', () => {
- describe('group level variables', () => {
- it('renders the environment dropdown', () => {
- createComponent(shallowMount, {
- isGroup: true,
- provide: {
- glFeatures: {
- groupScopedCiVariables: true,
- },
- },
- });
-
- expect(findCiEnvironmentsDropdown().exists()).toBe(true);
- expect(findCiEnvironmentsDropdown().isVisible()).toBe(true);
- });
-
- describe('licensed feature is not available', () => {
- it('disables the dropdown', () => {
- createComponent(mount, {
- isGroup: true,
- provide: {
- glFeatures: {
- groupScopedCiVariables: false,
- },
- },
- });
-
- const environmentScopeInput = wrapper
- .find('[data-testid="environment-scope"]')
- .findComponent(GlFormInput);
- expect(findCiEnvironmentsDropdown().exists()).toBe(false);
- expect(environmentScopeInput.attributes('readonly')).toBe('readonly');
- });
- });
- });
-
- it('renders a link to documentation on scopes', () => {
- createComponent(mount);
-
- const link = wrapper.find('[data-testid="environment-scope-link"]');
-
- expect(link.attributes('title')).toBe(ENVIRONMENT_SCOPE_LINK_TITLE);
- expect(link.attributes('href')).toBe('/help/environments');
- });
- });
-
- describe('Validations', () => {
- const maskError = 'This variable can not be masked.';
-
- describe('when the mask state is invalid', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const invalidMaskVariable = {
- ...variable,
- key: 'qs',
- value: 'd:;',
- secret_value: 'd:;',
- masked: true,
- };
- createComponent(mount);
- store.state.variable = invalidMaskVariable;
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- it('disables the submit button', () => {
- expect(findAddorUpdateButton().attributes('disabled')).toBe('disabled');
- });
-
- it('shows the correct error text', () => {
- expect(findModal().text()).toContain(maskError);
- });
-
- it('sends the correct tracking event', () => {
- expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, {
- label: EVENT_LABEL,
- property: ';',
- });
- });
- });
-
- describe.each`
- value | secret | masked | eventSent | trackingErrorProperty
- ${'value'} | ${'secretValue'} | ${false} | ${0} | ${null}
- ${'shortMasked'} | ${'short'} | ${true} | ${0} | ${null}
- ${'withDollar$Sign'} | ${'dollar$ign'} | ${false} | ${1} | ${'$'}
- ${'withDollar$Sign'} | ${'dollar$ign'} | ${true} | ${1} | ${'$'}
- ${'unsupported'} | ${'unsupported|char'} | ${true} | ${1} | ${'|'}
- ${'unsupportedMasked'} | ${'unsupported|char'} | ${false} | ${0} | ${null}
- `('Adding a new variable', ({ value, secret, masked, eventSent, trackingErrorProperty }) => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const invalidKeyVariable = {
- ...variable,
- key: 'key',
- value,
- secret_value: secret,
- masked,
- };
- createComponent(mount);
- store.state.variable = invalidKeyVariable;
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- it(`${
- eventSent > 0 ? 'sends the correct' : 'does not send the'
- } variable validation tracking event`, () => {
- expect(trackingSpy).toHaveBeenCalledTimes(eventSent);
-
- if (eventSent > 0) {
- expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, {
- label: EVENT_LABEL,
- property: trackingErrorProperty,
- });
- }
- });
- });
-
- describe('when both states are valid', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const validMaskandKeyVariable = {
- ...variable,
- key: AWS_ACCESS_KEY_ID,
- value: '12345678',
- secret_value: '87654321',
- masked: true,
- };
- createComponent(mount);
- store.state.variable = validMaskandKeyVariable;
- });
-
- it('does not disable the submit button', () => {
- expect(findAddorUpdateButton().attributes('disabled')).toBeUndefined();
- });
- });
- });
-});
diff --git a/spec/frontend/ci_variable_list/components/legacy_ci_variable_settings_spec.js b/spec/frontend/ci_variable_list/components/legacy_ci_variable_settings_spec.js
deleted file mode 100644
index 7def4dd4f29..00000000000
--- a/spec/frontend/ci_variable_list/components/legacy_ci_variable_settings_spec.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-import Vuex from 'vuex';
-import LegacyCiVariableSettings from '~/ci_variable_list/components/legacy_ci_variable_settings.vue';
-import createStore from '~/ci_variable_list/store';
-
-Vue.use(Vuex);
-
-describe('Ci variable table', () => {
- let wrapper;
- let store;
- let isProject;
-
- const createComponent = (projectState) => {
- store = createStore();
- store.state.isProject = projectState;
- jest.spyOn(store, 'dispatch').mockImplementation();
- wrapper = shallowMount(LegacyCiVariableSettings, {
- store,
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('dispatches fetchEnvironments when mounted', () => {
- isProject = true;
- createComponent(isProject);
- expect(store.dispatch).toHaveBeenCalledWith('fetchEnvironments');
- });
-
- it('does not dispatch fetchenvironments when in group context', () => {
- isProject = false;
- createComponent(isProject);
- expect(store.dispatch).not.toHaveBeenCalled();
- });
-});
diff --git a/spec/frontend/ci_variable_list/components/legacy_ci_variable_table_spec.js b/spec/frontend/ci_variable_list/components/legacy_ci_variable_table_spec.js
deleted file mode 100644
index 310afc8003a..00000000000
--- a/spec/frontend/ci_variable_list/components/legacy_ci_variable_table_spec.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
-import LegacyCiVariableTable from '~/ci_variable_list/components/legacy_ci_variable_table.vue';
-import createStore from '~/ci_variable_list/store';
-import mockData from '../services/mock_data';
-
-Vue.use(Vuex);
-
-describe('Ci variable table', () => {
- let wrapper;
- let store;
-
- const createComponent = () => {
- store = createStore();
- jest.spyOn(store, 'dispatch').mockImplementation();
- wrapper = mountExtended(LegacyCiVariableTable, {
- attachTo: document.body,
- store,
- });
- };
-
- const findRevealButton = () => wrapper.findByText('Reveal values');
- const findEditButton = () => wrapper.findByLabelText('Edit');
- const findEmptyVariablesPlaceholder = () => wrapper.findByText('There are no variables yet.');
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('dispatches fetchVariables when mounted', () => {
- expect(store.dispatch).toHaveBeenCalledWith('fetchVariables');
- });
-
- describe('When table is empty', () => {
- beforeEach(() => {
- store.state.variables = [];
- });
-
- it('displays empty message', () => {
- expect(findEmptyVariablesPlaceholder().exists()).toBe(true);
- });
-
- it('hides the reveal button', () => {
- expect(findRevealButton().exists()).toBe(false);
- });
- });
-
- describe('When table has variables', () => {
- beforeEach(() => {
- store.state.variables = mockData.mockVariables;
- });
-
- it('does not display the empty message', () => {
- expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
- });
-
- it('displays the reveal button', () => {
- expect(findRevealButton().exists()).toBe(true);
- });
-
- it('displays the correct amount of variables', async () => {
- expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(1);
- });
- });
-
- describe('Table click actions', () => {
- beforeEach(() => {
- store.state.variables = mockData.mockVariables;
- });
-
- it('reveals secret values when button is clicked', () => {
- findRevealButton().trigger('click');
- expect(store.dispatch).toHaveBeenCalledWith('toggleValues', false);
- });
-
- it('dispatches editVariable with correct variable to edit', () => {
- findEditButton().trigger('click');
- expect(store.dispatch).toHaveBeenCalledWith('editVariable', mockData.mockVariables[0]);
- });
- });
-});
diff --git a/spec/frontend/ci_variable_list/store/actions_spec.js b/spec/frontend/ci_variable_list/store/actions_spec.js
deleted file mode 100644
index e8c81a53a55..00000000000
--- a/spec/frontend/ci_variable_list/store/actions_spec.js
+++ /dev/null
@@ -1,319 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
-import Api from '~/api';
-import * as actions from '~/ci_variable_list/store/actions';
-import * as types from '~/ci_variable_list/store/mutation_types';
-import getInitialState from '~/ci_variable_list/store/state';
-import { prepareDataForDisplay, prepareEnvironments } from '~/ci_variable_list/store/utils';
-import { createAlert } from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import mockData from '../services/mock_data';
-
-jest.mock('~/api.js');
-jest.mock('~/flash.js');
-
-describe('CI variable list store actions', () => {
- let mock;
- let state;
- const mockVariable = {
- environment_scope: '*',
- id: 63,
- key: 'test_var',
- masked: false,
- protected: false,
- value: 'test_val',
- variable_type: 'env_var',
- _destory: true,
- };
- const payloadError = new Error('Request failed with status code 500');
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- state = getInitialState();
- state.endpoint = '/variables';
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('toggleValues', () => {
- const valuesHidden = false;
- it('commits TOGGLE_VALUES mutation', () => {
- testAction(actions.toggleValues, valuesHidden, {}, [
- {
- type: types.TOGGLE_VALUES,
- payload: valuesHidden,
- },
- ]);
- });
- });
-
- describe('clearModal', () => {
- it('commits CLEAR_MODAL mutation', () => {
- testAction(actions.clearModal, {}, {}, [
- {
- type: types.CLEAR_MODAL,
- },
- ]);
- });
- });
-
- describe('resetEditing', () => {
- it('commits RESET_EDITING mutation', () => {
- testAction(
- actions.resetEditing,
- {},
- {},
- [
- {
- type: types.RESET_EDITING,
- },
- ],
- [{ type: 'fetchVariables' }],
- );
- });
- });
-
- describe('setVariableProtected', () => {
- it('commits SET_VARIABLE_PROTECTED mutation', () => {
- testAction(actions.setVariableProtected, {}, {}, [
- {
- type: types.SET_VARIABLE_PROTECTED,
- },
- ]);
- });
- });
-
- describe('deleteVariable', () => {
- it('dispatch correct actions on successful deleted variable', () => {
- mock.onPatch(state.endpoint).reply(200);
-
- return testAction(
- actions.deleteVariable,
- {},
- state,
- [],
- [
- { type: 'requestDeleteVariable' },
- { type: 'receiveDeleteVariableSuccess' },
- { type: 'fetchVariables' },
- ],
- );
- });
-
- it('should show flash error and set error in state on delete failure', async () => {
- mock.onPatch(state.endpoint).reply(500, '');
-
- await testAction(
- actions.deleteVariable,
- {},
- state,
- [],
- [
- { type: 'requestDeleteVariable' },
- {
- type: 'receiveDeleteVariableError',
- payload: payloadError,
- },
- ],
- );
- expect(createAlert).toHaveBeenCalled();
- });
- });
-
- describe('updateVariable', () => {
- it('dispatch correct actions on successful updated variable', () => {
- mock.onPatch(state.endpoint).reply(200);
-
- return testAction(
- actions.updateVariable,
- {},
- state,
- [],
- [
- { type: 'requestUpdateVariable' },
- { type: 'receiveUpdateVariableSuccess' },
- { type: 'fetchVariables' },
- ],
- );
- });
-
- it('should show flash error and set error in state on update failure', async () => {
- mock.onPatch(state.endpoint).reply(500, '');
-
- await testAction(
- actions.updateVariable,
- mockVariable,
- state,
- [],
- [
- { type: 'requestUpdateVariable' },
- {
- type: 'receiveUpdateVariableError',
- payload: payloadError,
- },
- ],
- );
- expect(createAlert).toHaveBeenCalled();
- });
- });
-
- describe('addVariable', () => {
- it('dispatch correct actions on successful added variable', () => {
- mock.onPatch(state.endpoint).reply(200);
-
- return testAction(
- actions.addVariable,
- {},
- state,
- [],
- [
- { type: 'requestAddVariable' },
- { type: 'receiveAddVariableSuccess' },
- { type: 'fetchVariables' },
- ],
- );
- });
-
- it('should show flash error and set error in state on add failure', async () => {
- mock.onPatch(state.endpoint).reply(500, '');
-
- await testAction(
- actions.addVariable,
- {},
- state,
- [],
- [
- { type: 'requestAddVariable' },
- {
- type: 'receiveAddVariableError',
- payload: payloadError,
- },
- ],
- );
- expect(createAlert).toHaveBeenCalled();
- });
- });
-
- describe('fetchVariables', () => {
- it('dispatch correct actions on fetchVariables', () => {
- mock.onGet(state.endpoint).reply(200, { variables: mockData.mockVariables });
-
- return testAction(
- actions.fetchVariables,
- {},
- state,
- [],
- [
- { type: 'requestVariables' },
- {
- type: 'receiveVariablesSuccess',
- payload: prepareDataForDisplay(mockData.mockVariables),
- },
- ],
- );
- });
-
- it('should show flash error and set error in state on fetch variables failure', async () => {
- mock.onGet(state.endpoint).reply(500);
-
- await testAction(actions.fetchVariables, {}, state, [], [{ type: 'requestVariables' }]);
- expect(createAlert).toHaveBeenCalledWith({
- message: 'There was an error fetching the variables.',
- });
- });
- });
-
- describe('fetchEnvironments', () => {
- it('dispatch correct actions on fetchEnvironments', () => {
- Api.environments = jest.fn().mockResolvedValue({ data: mockData.mockEnvironments });
-
- return testAction(
- actions.fetchEnvironments,
- {},
- state,
- [],
- [
- { type: 'requestEnvironments' },
- {
- type: 'receiveEnvironmentsSuccess',
- payload: prepareEnvironments(mockData.mockEnvironments),
- },
- ],
- );
- });
-
- it('should show flash error and set error in state on fetch environments failure', async () => {
- Api.environments = jest.fn().mockRejectedValue();
-
- await testAction(actions.fetchEnvironments, {}, state, [], [{ type: 'requestEnvironments' }]);
-
- expect(createAlert).toHaveBeenCalledWith({
- message: 'There was an error fetching the environments information.',
- });
- });
- });
-
- describe('Update variable values', () => {
- it('updateVariableKey', () => {
- testAction(
- actions.updateVariableKey,
- { key: mockVariable.key },
- {},
- [
- {
- type: types.UPDATE_VARIABLE_KEY,
- payload: mockVariable.key,
- },
- ],
- [],
- );
- });
-
- it('updateVariableValue', () => {
- testAction(
- actions.updateVariableValue,
- { secret_value: mockVariable.value },
- {},
- [
- {
- type: types.UPDATE_VARIABLE_VALUE,
- payload: mockVariable.value,
- },
- ],
- [],
- );
- });
-
- it('updateVariableType', () => {
- testAction(
- actions.updateVariableType,
- { variable_type: mockVariable.variable_type },
- {},
- [{ type: types.UPDATE_VARIABLE_TYPE, payload: mockVariable.variable_type }],
- [],
- );
- });
-
- it('updateVariableProtected', () => {
- testAction(
- actions.updateVariableProtected,
- { protected_variable: mockVariable.protected },
- {},
- [{ type: types.UPDATE_VARIABLE_PROTECTED, payload: mockVariable.protected }],
- [],
- );
- });
-
- it('updateVariableMasked', () => {
- testAction(
- actions.updateVariableMasked,
- { masked: mockVariable.masked },
- {},
- [{ type: types.UPDATE_VARIABLE_MASKED, payload: mockVariable.masked }],
- [],
- );
- });
- });
-});
diff --git a/spec/frontend/ci_variable_list/store/getters_spec.js b/spec/frontend/ci_variable_list/store/getters_spec.js
deleted file mode 100644
index 92f22b18763..00000000000
--- a/spec/frontend/ci_variable_list/store/getters_spec.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import * as getters from '~/ci_variable_list/store/getters';
-import mockData from '../services/mock_data';
-
-describe('Ci variable getters', () => {
- describe('joinedEnvironments', () => {
- it('should join fetched environments with variable environment scopes', () => {
- const state = {
- environments: ['All (default)', 'staging', 'deployment', 'prod'],
- variables: mockData.mockVariableScopes,
- };
-
- expect(getters.joinedEnvironments(state)).toEqual([
- 'All (default)',
- 'deployment',
- 'prod',
- 'production',
- 'staging',
- ]);
- });
- });
-});
diff --git a/spec/frontend/ci_variable_list/store/mutations_spec.js b/spec/frontend/ci_variable_list/store/mutations_spec.js
deleted file mode 100644
index c7d07ead09b..00000000000
--- a/spec/frontend/ci_variable_list/store/mutations_spec.js
+++ /dev/null
@@ -1,136 +0,0 @@
-import * as types from '~/ci_variable_list/store/mutation_types';
-import mutations from '~/ci_variable_list/store/mutations';
-import state from '~/ci_variable_list/store/state';
-
-describe('CI variable list mutations', () => {
- let stateCopy;
-
- beforeEach(() => {
- stateCopy = state();
- });
-
- describe('TOGGLE_VALUES', () => {
- it('should toggle state', () => {
- const valuesHidden = false;
-
- mutations[types.TOGGLE_VALUES](stateCopy, valuesHidden);
-
- expect(stateCopy.valuesHidden).toEqual(valuesHidden);
- });
- });
-
- describe('VARIABLE_BEING_EDITED', () => {
- it('should set the variable that is being edited', () => {
- mutations[types.VARIABLE_BEING_EDITED](stateCopy);
-
- expect(stateCopy.variableBeingEdited).toBe(true);
- });
- });
-
- describe('RESET_EDITING', () => {
- it('should reset variableBeingEdited to false', () => {
- mutations[types.RESET_EDITING](stateCopy);
-
- expect(stateCopy.variableBeingEdited).toBe(false);
- });
- });
-
- describe('CLEAR_MODAL', () => {
- it('should clear modal state', () => {
- const modalState = {
- variable_type: 'Variable',
- key: '',
- secret_value: '',
- protected_variable: false,
- masked: false,
- environment_scope: 'All (default)',
- };
-
- mutations[types.CLEAR_MODAL](stateCopy);
-
- expect(stateCopy.variable).toEqual(modalState);
- });
- });
-
- describe('RECEIVE_ENVIRONMENTS_SUCCESS', () => {
- it('should set environments', () => {
- const environments = ['env1', 'env2'];
-
- mutations[types.RECEIVE_ENVIRONMENTS_SUCCESS](stateCopy, environments);
-
- expect(stateCopy.environments).toEqual(['All (default)', 'env1', 'env2']);
- });
- });
-
- describe('SET_ENVIRONMENT_SCOPE', () => {
- const environment = 'production';
-
- it('should set environment scope on variable', () => {
- mutations[types.SET_ENVIRONMENT_SCOPE](stateCopy, environment);
-
- expect(stateCopy.variable.environment_scope).toBe('production');
- });
- });
-
- describe('ADD_WILD_CARD_SCOPE', () => {
- it('should add wild card scope to environments array and sort', () => {
- stateCopy.environments = ['dev', 'staging'];
- mutations[types.ADD_WILD_CARD_SCOPE](stateCopy, 'production');
-
- expect(stateCopy.environments).toEqual(['dev', 'production', 'staging']);
- });
- });
-
- describe('SET_VARIABLE_PROTECTED', () => {
- it('should set protected value to true', () => {
- mutations[types.SET_VARIABLE_PROTECTED](stateCopy);
-
- expect(stateCopy.variable.protected_variable).toBe(true);
- });
- });
-
- describe('UPDATE_VARIABLE_KEY', () => {
- it('should update variable key value', () => {
- const key = 'new_var';
- mutations[types.UPDATE_VARIABLE_KEY](stateCopy, key);
-
- expect(stateCopy.variable.key).toBe(key);
- });
- });
-
- describe('UPDATE_VARIABLE_VALUE', () => {
- it('should update variable value', () => {
- const value = 'variable_value';
- mutations[types.UPDATE_VARIABLE_VALUE](stateCopy, value);
-
- expect(stateCopy.variable.secret_value).toBe(value);
- });
- });
-
- describe('UPDATE_VARIABLE_TYPE', () => {
- it('should update variable type value', () => {
- const type = 'File';
- mutations[types.UPDATE_VARIABLE_TYPE](stateCopy, type);
-
- expect(stateCopy.variable.variable_type).toBe(type);
- });
- });
-
- describe('UPDATE_VARIABLE_PROTECTED', () => {
- it('should update variable protected value', () => {
- const protectedValue = true;
- mutations[types.UPDATE_VARIABLE_PROTECTED](stateCopy, protectedValue);
-
- expect(stateCopy.variable.protected_variable).toBe(protectedValue);
- });
- });
-
- describe('UPDATE_VARIABLE_MASKED', () => {
- it('should update variable masked value', () => {
- const masked = true;
- mutations[types.UPDATE_VARIABLE_MASKED](stateCopy, masked);
-
- expect(stateCopy.variable.masked).toBe(masked);
- });
- });
-});
diff --git a/spec/frontend/ci_variable_list/store/utils_spec.js b/spec/frontend/ci_variable_list/store/utils_spec.js
deleted file mode 100644
index 5b10370324a..00000000000
--- a/spec/frontend/ci_variable_list/store/utils_spec.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import {
- prepareDataForDisplay,
- prepareEnvironments,
- prepareDataForApi,
-} from '~/ci_variable_list/store/utils';
-import mockData from '../services/mock_data';
-
-describe('CI variables store utils', () => {
- it('prepares ci variables for display', () => {
- expect(prepareDataForDisplay(mockData.mockVariablesApi)).toStrictEqual(
- mockData.mockVariablesDisplay,
- );
- });
-
- it('prepares single ci variable for api', () => {
- expect(prepareDataForApi(mockData.mockVariablesDisplay[0])).toStrictEqual({
- environment_scope: '*',
- id: 113,
- key: 'test_var',
- masked: 'false',
- protected: 'false',
- secret_value: 'test_val',
- value: 'test_val',
- variable_type: 'env_var',
- });
-
- expect(prepareDataForApi(mockData.mockVariablesDisplay[1])).toStrictEqual({
- environment_scope: '*',
- id: 114,
- key: 'test_var_2',
- masked: 'false',
- protected: 'false',
- secret_value: 'test_val_2',
- value: 'test_val_2',
- variable_type: 'file',
- });
- });
-
- it('prepares single ci variable for delete', () => {
- expect(prepareDataForApi(mockData.mockVariablesDisplay[0], true)).toHaveProperty(
- '_destroy',
- true,
- );
- });
-
- it('prepares environments for display', () => {
- expect(prepareEnvironments(mockData.mockEnvironments)).toStrictEqual(['staging', 'production']);
- });
-});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/forwarding_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/forwarding_settings_spec.js
new file mode 100644
index 00000000000..8f229182fe5
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/forwarding_settings_spec.js
@@ -0,0 +1,78 @@
+import { GlFormGroup, GlSprintf } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import component from '~/packages_and_registries/settings/group/components/forwarding_settings.vue';
+
+describe('Forwarding Settings', () => {
+ let wrapper;
+
+ const defaultProps = {
+ disabled: false,
+ forwarding: false,
+ label: 'label',
+ lockForwarding: false,
+ modelNames: {
+ forwarding: 'forwardField',
+ lockForwarding: 'lockForwardingField',
+ isLocked: 'lockedField',
+ },
+ };
+
+ const mountComponent = (propsData = defaultProps) => {
+ wrapper = shallowMountExtended(component, {
+ propsData,
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ const findFormGroup = () => wrapper.findComponent(GlFormGroup);
+ const findForwardingCheckbox = () => wrapper.findByTestId('forwarding-checkbox');
+ const findLockForwardingCheckbox = () => wrapper.findByTestId('lock-forwarding-checkbox');
+
+ it('has a form group', () => {
+ mountComponent();
+
+ expect(findFormGroup().exists()).toBe(true);
+ expect(findFormGroup().attributes()).toMatchObject({
+ label: defaultProps.label,
+ });
+ });
+
+ describe.each`
+ name | finder | label | extraProps | field
+ ${'forwarding'} | ${findForwardingCheckbox} | ${'Forward label package requests'} | ${{ forwarding: true }} | ${defaultProps.modelNames.forwarding}
+ ${'lock forwarding'} | ${findLockForwardingCheckbox} | ${'Enforce label setting for all subgroups'} | ${{ lockForwarding: true }} | ${defaultProps.modelNames.lockForwarding}
+ `('$name checkbox', ({ name, finder, label, extraProps, field }) => {
+ it('is rendered', () => {
+ mountComponent();
+ expect(finder().exists()).toBe(true);
+ expect(finder().text()).toMatchInterpolatedText(label);
+ expect(finder().attributes('disabled')).toBeUndefined();
+ expect(finder().attributes('checked')).toBeUndefined();
+ });
+
+ it(`is checked when ${name} set`, () => {
+ mountComponent({ ...defaultProps, ...extraProps });
+
+ expect(finder().attributes('checked')).toBe('true');
+ });
+
+ it(`emits an update event with field ${field} set`, () => {
+ mountComponent();
+
+ finder().vm.$emit('change', true);
+
+ expect(wrapper.emitted('update')).toStrictEqual([[field, true]]);
+ });
+ });
+
+ describe('disabled', () => {
+ it('disables both checkboxes', () => {
+ mountComponent({ ...defaultProps, disabled: true });
+
+ expect(findForwardingCheckbox().attributes('disabled')).toEqual('true');
+ expect(findLockForwardingCheckbox().attributes('disabled')).toEqual('true');
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
index 31fc3ad419c..7edc321867c 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
@@ -7,6 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import PackagesSettings from '~/packages_and_registries/settings/group/components/packages_settings.vue';
import DependencyProxySettings from '~/packages_and_registries/settings/group/components/dependency_proxy_settings.vue';
+import PackagesForwardingSettings from '~/packages_and_registries/settings/group/components/packages_forwarding_settings.vue';
import component from '~/packages_and_registries/settings/group/components/group_settings_app.vue';
@@ -60,6 +61,7 @@ describe('Group Settings App', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findPackageSettings = () => wrapper.findComponent(PackagesSettings);
+ const findPackageForwardingSettings = () => wrapper.findComponent(PackagesForwardingSettings);
const findDependencyProxySettings = () => wrapper.findComponent(DependencyProxySettings);
const waitForApolloQueryAndRender = async () => {
@@ -67,16 +69,18 @@ describe('Group Settings App', () => {
await nextTick();
};
- const packageSettingsProps = { packageSettings: packageSettings() };
+ const packageSettingsProps = { packageSettings };
+ const packageForwardingSettingsProps = { forwardSettings: { ...packageSettings } };
const dependencyProxyProps = {
dependencyProxySettings: dependencyProxySettings(),
dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
};
describe.each`
- finder | entitySpecificProps | successMessage | errorMessage
- ${findPackageSettings} | ${packageSettingsProps} | ${'Settings saved successfully'} | ${'An error occurred while saving the settings'}
- ${findDependencyProxySettings} | ${dependencyProxyProps} | ${'Setting saved successfully'} | ${'An error occurred while saving the setting'}
+ finder | entitySpecificProps | successMessage | errorMessage
+ ${findPackageSettings} | ${packageSettingsProps} | ${'Settings saved successfully'} | ${'An error occurred while saving the settings'}
+ ${findPackageForwardingSettings} | ${packageForwardingSettingsProps} | ${'Settings saved successfully'} | ${'An error occurred while saving the settings'}
+ ${findDependencyProxySettings} | ${dependencyProxyProps} | ${'Setting saved successfully'} | ${'An error occurred while saving the setting'}
`('settings blocks', ({ finder, entitySpecificProps, successMessage, errorMessage }) => {
beforeEach(() => {
mountComponent();
@@ -88,10 +92,7 @@ describe('Group Settings App', () => {
});
it('binds the correctProps', () => {
- expect(finder().props()).toMatchObject({
- isLoading: false,
- ...entitySpecificProps,
- });
+ expect(finder().props()).toMatchObject(entitySpecificProps);
});
describe('success event', () => {
diff --git a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
index 13eba39ec8c..807f332f4d3 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
@@ -48,7 +48,7 @@ describe('Packages Settings', () => {
apolloProvider,
provide: defaultProvide,
propsData: {
- packageSettings: packageSettings(),
+ packageSettings,
},
stubs: {
SettingsBlock,
@@ -83,7 +83,7 @@ describe('Packages Settings', () => {
};
const emitMavenSettingsUpdate = (override) => {
- findGenericDuplicatedSettingsExceptionsInput().vm.$emit('update', {
+ findMavenDuplicatedSettingsExceptionsInput().vm.$emit('update', {
mavenDuplicateExceptionRegex: ')',
...override,
});
@@ -117,7 +117,7 @@ describe('Packages Settings', () => {
it('renders toggle', () => {
mountComponent({ mountFn: mountExtended });
- const { mavenDuplicatesAllowed } = packageSettings();
+ const { mavenDuplicatesAllowed } = packageSettings;
expect(findMavenDuplicatedSettingsToggle().exists()).toBe(true);
@@ -132,7 +132,7 @@ describe('Packages Settings', () => {
it('renders ExceptionsInput and assigns duplication allowness and exception props', () => {
mountComponent({ mountFn: mountExtended });
- const { mavenDuplicatesAllowed, mavenDuplicateExceptionRegex } = packageSettings();
+ const { mavenDuplicatesAllowed, mavenDuplicateExceptionRegex } = packageSettings;
expect(findMavenDuplicatedSettingsExceptionsInput().exists()).toBe(true);
@@ -170,7 +170,7 @@ describe('Packages Settings', () => {
it('renders toggle', () => {
mountComponent({ mountFn: mountExtended });
- const { genericDuplicatesAllowed } = packageSettings();
+ const { genericDuplicatesAllowed } = packageSettings;
expect(findGenericDuplicatedSettingsToggle().exists()).toBe(true);
expect(findGenericDuplicatedSettingsToggle().props()).toMatchObject({
@@ -184,7 +184,7 @@ describe('Packages Settings', () => {
it('renders ExceptionsInput and assigns duplication allowness and exception props', async () => {
mountComponent({ mountFn: mountExtended });
- const { genericDuplicatesAllowed, genericDuplicateExceptionRegex } = packageSettings();
+ const { genericDuplicatesAllowed, genericDuplicateExceptionRegex } = packageSettings;
expect(findGenericDuplicatedSettingsExceptionsInput().props()).toMatchObject({
duplicatesAllowed: genericDuplicatesAllowed,
@@ -239,7 +239,7 @@ describe('Packages Settings', () => {
emitMavenSettingsUpdate({ mavenDuplicateExceptionRegex });
expect(updateGroupPackagesSettingsOptimisticResponse).toHaveBeenCalledWith({
- ...packageSettings(),
+ ...packageSettings,
mavenDuplicateExceptionRegex,
});
});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js
new file mode 100644
index 00000000000..a0b257a9496
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js
@@ -0,0 +1,280 @@
+import Vue from 'vue';
+import { GlButton } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import component from '~/packages_and_registries/settings/group/components/packages_forwarding_settings.vue';
+import {
+ PACKAGE_FORWARDING_SETTINGS_DESCRIPTION,
+ PACKAGE_FORWARDING_SETTINGS_HEADER,
+} from '~/packages_and_registries/settings/group/constants';
+
+import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql';
+import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
+import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
+import { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
+import {
+ packageSettings,
+ packageForwardingSettings,
+ groupPackageSettingsMock,
+ groupPackageForwardSettingsMutationMock,
+ mutationErrorMock,
+ npmProps,
+ pypiProps,
+ mavenProps,
+} from '../mock_data';
+
+jest.mock('~/flash');
+jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses');
+
+describe('Packages Forwarding Settings', () => {
+ let wrapper;
+ let apolloProvider;
+ const mutationResolverFn = jest.fn().mockResolvedValue(groupPackageForwardSettingsMutationMock());
+
+ const defaultProvide = {
+ groupPath: 'foo_group_path',
+ };
+
+ const mountComponent = ({
+ forwardSettings = { ...packageSettings },
+ features = {},
+ mutationResolver = mutationResolverFn,
+ } = {}) => {
+ Vue.use(VueApollo);
+
+ const requestHandlers = [[updateNamespacePackageSettings, mutationResolver]];
+
+ apolloProvider = createMockApollo(requestHandlers);
+
+ wrapper = shallowMountExtended(component, {
+ apolloProvider,
+ provide: {
+ ...defaultProvide,
+ glFeatures: {
+ ...features,
+ },
+ },
+ propsData: {
+ forwardSettings,
+ },
+ stubs: {
+ SettingsBlock,
+ },
+ });
+ };
+
+ const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
+ const findForm = () => wrapper.find('form');
+ const findSubmitButton = () => findForm().findComponent(GlButton);
+ const findDescription = () => wrapper.findByTestId('description');
+ const findMavenForwardingSettings = () => wrapper.findByTestId('maven');
+ const findNpmForwardingSettings = () => wrapper.findByTestId('npm');
+ const findPyPiForwardingSettings = () => wrapper.findByTestId('pypi');
+
+ const fillApolloCache = () => {
+ apolloProvider.defaultClient.cache.writeQuery({
+ query: getGroupPackagesSettingsQuery,
+ variables: {
+ fullPath: defaultProvide.groupPath,
+ },
+ ...groupPackageSettingsMock,
+ });
+ };
+
+ const updateNpmSettings = () => {
+ findNpmForwardingSettings().vm.$emit('update', 'npmPackageRequestsForwarding', false);
+ };
+
+ const submitForm = () => {
+ findForm().trigger('submit');
+ return waitForPromises();
+ };
+
+ afterEach(() => {
+ apolloProvider = null;
+ });
+
+ it('renders a settings block', () => {
+ mountComponent();
+
+ expect(findSettingsBlock().exists()).toBe(true);
+ });
+
+ it('has the correct header text', () => {
+ mountComponent();
+
+ expect(wrapper.text()).toContain(PACKAGE_FORWARDING_SETTINGS_HEADER);
+ });
+
+ it('has the correct description text', () => {
+ mountComponent();
+
+ expect(findDescription().text()).toMatchInterpolatedText(
+ PACKAGE_FORWARDING_SETTINGS_DESCRIPTION,
+ );
+ });
+
+ it('watches changes to props', async () => {
+ mountComponent();
+
+ expect(findNpmForwardingSettings().props()).toMatchObject(npmProps);
+
+ await wrapper.setProps({
+ forwardSettings: {
+ ...packageSettings,
+ npmPackageRequestsForwardingLocked: true,
+ },
+ });
+
+ expect(findNpmForwardingSettings().props()).toMatchObject({ ...npmProps, disabled: true });
+ });
+
+ it('submit button is disabled', () => {
+ mountComponent();
+
+ expect(findSubmitButton().props('disabled')).toBe(true);
+ });
+
+ describe.each`
+ type | finder | props | field
+ ${'npm'} | ${findNpmForwardingSettings} | ${npmProps} | ${'npmPackageRequestsForwarding'}
+ ${'pypi'} | ${findPyPiForwardingSettings} | ${pypiProps} | ${'pypiPackageRequestsForwarding'}
+ ${'maven'} | ${findMavenForwardingSettings} | ${mavenProps} | ${'mavenPackageRequestsForwarding'}
+ `('$type settings', ({ finder, props, field }) => {
+ beforeEach(() => {
+ mountComponent({ features: { mavenCentralRequestForwarding: true } });
+ });
+
+ it('assigns forwarding settings props', () => {
+ expect(finder().props()).toMatchObject(props);
+ });
+
+ it('on update event enables submit button', async () => {
+ finder().vm.$emit('update', field, false);
+
+ await waitForPromises();
+
+ expect(findSubmitButton().props('disabled')).toBe(false);
+ });
+ });
+
+ describe('maven settings', () => {
+ describe('with feature turned off', () => {
+ it('does not exist', () => {
+ mountComponent();
+
+ expect(findMavenForwardingSettings().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('settings update', () => {
+ describe('success state', () => {
+ it('calls the mutation with the right variables', async () => {
+ const {
+ mavenPackageRequestsForwardingLocked,
+ npmPackageRequestsForwardingLocked,
+ pypiPackageRequestsForwardingLocked,
+ ...packageSettingsInput
+ } = packageForwardingSettings;
+
+ mountComponent();
+
+ fillApolloCache();
+ updateNpmSettings();
+
+ await submitForm();
+
+ expect(mutationResolverFn).toHaveBeenCalledWith({
+ input: {
+ namespacePath: defaultProvide.groupPath,
+ ...packageSettingsInput,
+ npmPackageRequestsForwarding: false,
+ },
+ });
+ });
+
+ it('when field are locked calls the mutation with the right variables', async () => {
+ mountComponent({
+ forwardSettings: {
+ ...packageSettings,
+ mavenPackageRequestsForwardingLocked: true,
+ pypiPackageRequestsForwardingLocked: true,
+ },
+ });
+
+ fillApolloCache();
+ updateNpmSettings();
+
+ await submitForm();
+
+ expect(mutationResolverFn).toHaveBeenCalledWith({
+ input: {
+ namespacePath: defaultProvide.groupPath,
+ lockNpmPackageRequestsForwarding: false,
+ npmPackageRequestsForwarding: false,
+ },
+ });
+ });
+
+ it('emits a success event', async () => {
+ mountComponent();
+ fillApolloCache();
+ updateNpmSettings();
+
+ await submitForm();
+
+ expect(wrapper.emitted('success')).toHaveLength(1);
+ });
+
+ it('has an optimistic response', async () => {
+ const npmPackageRequestsForwarding = false;
+ mountComponent();
+
+ fillApolloCache();
+
+ expect(findNpmForwardingSettings().props('forwarding')).toBe(true);
+
+ updateNpmSettings();
+ await submitForm();
+
+ expect(updateGroupPackagesSettingsOptimisticResponse).toHaveBeenCalledWith({
+ ...packageSettings,
+ npmPackageRequestsForwarding,
+ });
+ expect(findNpmForwardingSettings().props('forwarding')).toBe(npmPackageRequestsForwarding);
+ });
+ });
+
+ describe('errors', () => {
+ it('mutation payload with root level errors', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(mutationErrorMock);
+ mountComponent({ mutationResolver });
+
+ fillApolloCache();
+
+ updateNpmSettings();
+ await submitForm();
+
+ expect(wrapper.emitted('error')).toHaveLength(1);
+ });
+
+ it.each`
+ type | mutationResolver
+ ${'local'} | ${jest.fn().mockResolvedValue(groupPackageForwardSettingsMutationMock({ errors: ['foo'] }))}
+ ${'network'} | ${jest.fn().mockRejectedValue()}
+ `('mutation payload with $type error', async ({ mutationResolver }) => {
+ mountComponent({ mutationResolver });
+
+ fillApolloCache();
+
+ updateNpmSettings();
+ await submitForm();
+
+ expect(wrapper.emitted('error')).toHaveLength(1);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/mock_data.js b/spec/frontend/packages_and_registries/settings/group/mock_data.js
index d53446de910..1ca9dc6daeb 100644
--- a/spec/frontend/packages_and_registries/settings/group/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/group/mock_data.js
@@ -1,9 +1,26 @@
-export const packageSettings = () => ({
+const packageDuplicateSettings = {
mavenDuplicatesAllowed: true,
mavenDuplicateExceptionRegex: '',
genericDuplicatesAllowed: true,
genericDuplicateExceptionRegex: '',
-});
+};
+
+export const packageForwardingSettings = {
+ mavenPackageRequestsForwarding: true,
+ lockMavenPackageRequestsForwarding: false,
+ npmPackageRequestsForwarding: true,
+ lockNpmPackageRequestsForwarding: false,
+ pypiPackageRequestsForwarding: true,
+ lockPypiPackageRequestsForwarding: false,
+ mavenPackageRequestsForwardingLocked: false,
+ npmPackageRequestsForwardingLocked: false,
+ pypiPackageRequestsForwardingLocked: false,
+};
+
+export const packageSettings = {
+ ...packageDuplicateSettings,
+ ...packageForwardingSettings,
+};
export const dependencyProxySettings = (extend) => ({
enabled: true,
@@ -21,13 +38,52 @@ export const groupPackageSettingsMock = {
group: {
id: '1',
fullPath: 'foo_group_path',
- packageSettings: packageSettings(),
+ packageSettings: {
+ ...packageSettings,
+ __typename: 'PackageSettings',
+ },
dependencyProxySetting: dependencyProxySettings(),
dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
},
},
};
+export const npmProps = {
+ forwarding: packageForwardingSettings.npmPackageRequestsForwarding,
+ lockForwarding: packageForwardingSettings.lockNpmPackageRequestsForwarding,
+ label: 'npm',
+ disabled: false,
+ modelNames: {
+ forwarding: 'npmPackageRequestsForwarding',
+ lockForwarding: 'lockNpmPackageRequestsForwarding',
+ isLocked: 'npmPackageRequestsForwardingLocked',
+ },
+};
+
+export const pypiProps = {
+ forwarding: packageForwardingSettings.pypiPackageRequestsForwarding,
+ lockForwarding: packageForwardingSettings.lockPypiPackageRequestsForwarding,
+ label: 'PyPI',
+ disabled: false,
+ modelNames: {
+ forwarding: 'pypiPackageRequestsForwarding',
+ lockForwarding: 'lockPypiPackageRequestsForwarding',
+ isLocked: 'pypiPackageRequestsForwardingLocked',
+ },
+};
+
+export const mavenProps = {
+ forwarding: packageForwardingSettings.mavenPackageRequestsForwarding,
+ lockForwarding: packageForwardingSettings.lockMavenPackageRequestsForwarding,
+ label: 'Maven',
+ disabled: false,
+ modelNames: {
+ forwarding: 'mavenPackageRequestsForwarding',
+ lockForwarding: 'lockMavenPackageRequestsForwarding',
+ isLocked: 'mavenPackageRequestsForwardingLocked',
+ },
+};
+
export const groupPackageSettingsMutationMock = (override) => ({
data: {
updateNamespacePackageSettings: {
@@ -43,6 +99,19 @@ export const groupPackageSettingsMutationMock = (override) => ({
},
});
+export const groupPackageForwardSettingsMutationMock = (override) => ({
+ data: {
+ updateNamespacePackageSettings: {
+ packageSettings: {
+ npmPackageRequestsForwarding: true,
+ lockNpmPackageRequestsForwarding: false,
+ },
+ errors: [],
+ ...override,
+ },
+ },
+});
+
export const dependencyProxySettingMutationMock = (override) => ({
data: {
updateDependencyProxySettings: {