Commit 5d0198ee authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '322841-move-dependency-proxy-enable-under-settings-packages-registries' into 'master'

Refactor package settings to own component

See merge request gitlab-org/gitlab!70925
parents 9aea45fe 93cd746a
<script> <script>
import { GlAlert, GlFormGroup, GlFormInputGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui'; import { GlAlert, GlFormGroup, GlFormInputGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { __ } from '~/locale'; import { __ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import {
DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
DEPENDENCY_PROXY_DOCS_PATH,
} from '~/packages_and_registries/settings/group/constants';
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql'; import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
...@@ -19,9 +22,6 @@ export default { ...@@ -19,9 +22,6 @@ export default {
}, },
inject: ['groupPath', 'dependencyProxyAvailable'], inject: ['groupPath', 'dependencyProxyAvailable'],
i18n: { i18n: {
subTitle: __(
'Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies.',
),
proxyNotAvailableText: __('Dependency proxy feature is limited to public groups for now.'), proxyNotAvailableText: __('Dependency proxy feature is limited to public groups for now.'),
proxyImagePrefix: __('Dependency proxy image prefix'), proxyImagePrefix: __('Dependency proxy image prefix'),
copyImagePrefixText: __('Copy prefix'), copyImagePrefixText: __('Copy prefix'),
...@@ -47,8 +47,8 @@ export default { ...@@ -47,8 +47,8 @@ export default {
infoMessages() { infoMessages() {
return [ return [
{ {
text: this.$options.i18n.subTitle, text: DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
link: helpPagePath('user/packages/dependency_proxy/index'), link: DEPENDENCY_PROXY_DOCS_PATH,
}, },
]; ];
}, },
......
<script> <script>
import { GlSprintf, GlLink, GlAlert } from '@gitlab/ui'; import { GlAlert } from '@gitlab/ui';
import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
import { import {
PACKAGE_SETTINGS_HEADER,
PACKAGE_SETTINGS_DESCRIPTION,
PACKAGES_DOCS_PATH,
ERROR_UPDATING_SETTINGS, ERROR_UPDATING_SETTINGS,
SUCCESS_UPDATING_SETTINGS, SUCCESS_UPDATING_SETTINGS,
} from '~/packages_and_registries/settings/group/constants'; } from '~/packages_and_registries/settings/group/constants';
import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql'; import PackagesSettings from '~/packages_and_registries/settings/group/components/packages_settings.vue';
import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql'; import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update';
import { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
export default { export default {
name: 'GroupSettingsApp', name: 'GroupSettingsApp',
i18n: {
PACKAGE_SETTINGS_HEADER,
PACKAGE_SETTINGS_DESCRIPTION,
},
links: {
PACKAGES_DOCS_PATH,
},
components: { components: {
GlAlert, GlAlert,
GlSprintf, PackagesSettings,
GlLink,
SettingsBlock,
MavenSettings,
GenericSettings,
DuplicatesSettings,
}, },
inject: ['defaultExpanded', 'groupPath'], inject: ['groupPath'],
apollo: { apollo: {
packageSettings: { group: {
query: getGroupPackagesSettingsQuery, query: getGroupPackagesSettingsQuery,
variables() { variables() {
return { return {
fullPath: this.groupPath, fullPath: this.groupPath,
}; };
}, },
update(data) {
return data.group?.packageSettings;
},
}, },
}, },
data() { data() {
return { return {
packageSettings: {}, group: {},
errors: {},
alertMessage: null, alertMessage: null,
}; };
}, },
computed: { computed: {
packageSettings() {
return this.group?.packageSettings || {};
},
isLoading() { isLoading() {
return this.$apollo.queries.packageSettings.loading; return this.$apollo.queries.group.loading;
}, },
}, },
methods: { methods: {
dismissAlert() { dismissAlert() {
this.alertMessage = null; this.alertMessage = null;
}, },
updateSettings(payload) { handleSuccess() {
this.errors = {}; this.$toast.show(SUCCESS_UPDATING_SETTINGS);
return this.$apollo this.dismissAlert();
.mutate({ },
mutation: updateNamespacePackageSettings, handleError() {
variables: { this.alertMessage = ERROR_UPDATING_SETTINGS;
input: {
namespacePath: this.groupPath,
...payload,
},
},
update: updateGroupPackageSettings(this.groupPath),
optimisticResponse: updateGroupPackagesSettingsOptimisticResponse({
...this.packageSettings,
...payload,
}),
})
.then(({ data }) => {
if (data.updateNamespacePackageSettings?.errors?.length > 0) {
this.alertMessage = ERROR_UPDATING_SETTINGS;
} else {
this.dismissAlert();
this.$toast.show(SUCCESS_UPDATING_SETTINGS);
}
})
.catch((e) => {
if (e.graphQLErrors) {
e.graphQLErrors.forEach((error) => {
const [
{
path: [key],
message,
},
] = error.extensions.problems;
this.errors = { ...this.errors, [key]: message };
});
}
this.alertMessage = ERROR_UPDATING_SETTINGS;
});
}, },
}, },
}; };
...@@ -114,50 +60,11 @@ export default { ...@@ -114,50 +60,11 @@ export default {
{{ alertMessage }} {{ alertMessage }}
</gl-alert> </gl-alert>
<settings-block <packages-settings
:default-expanded="defaultExpanded" :package-settings="packageSettings"
data-qa-selector="package_registry_settings_content" :is-loading="isLoading"
> @success="handleSuccess"
<template #title> {{ $options.i18n.PACKAGE_SETTINGS_HEADER }}</template> @error="handleError"
<template #description> />
<span data-testid="description">
<gl-sprintf :message="$options.i18n.PACKAGE_SETTINGS_DESCRIPTION">
<template #link="{ content }">
<gl-link :href="$options.links.PACKAGES_DOCS_PATH" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</span>
</template>
<template #default>
<maven-settings data-testid="maven-settings">
<template #default="{ modelNames }">
<duplicates-settings
:duplicates-allowed="packageSettings.mavenDuplicatesAllowed"
:duplicate-exception-regex="packageSettings.mavenDuplicateExceptionRegex"
:duplicate-exception-regex-error="errors.mavenDuplicateExceptionRegex"
:model-names="modelNames"
:loading="isLoading"
toggle-qa-selector="allow_duplicates_toggle"
label-qa-selector="allow_duplicates_label"
@update="updateSettings"
/>
</template>
</maven-settings>
<generic-settings class="gl-mt-6" data-testid="generic-settings">
<template #default="{ modelNames }">
<duplicates-settings
:duplicates-allowed="packageSettings.genericDuplicatesAllowed"
:duplicate-exception-regex="packageSettings.genericDuplicateExceptionRegex"
:duplicate-exception-regex-error="errors.genericDuplicateExceptionRegex"
:model-names="modelNames"
:loading="isLoading"
@update="updateSettings"
/>
</template>
</generic-settings>
</template>
</settings-block>
</div> </div>
</template> </template>
<script>
import { GlSprintf, GlLink } from '@gitlab/ui';
import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
import {
PACKAGE_SETTINGS_HEADER,
PACKAGE_SETTINGS_DESCRIPTION,
PACKAGES_DOCS_PATH,
} from '~/packages_and_registries/settings/group/constants';
import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql';
import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update';
import { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
export default {
name: 'PackageSettings',
i18n: {
PACKAGE_SETTINGS_HEADER,
PACKAGE_SETTINGS_DESCRIPTION,
},
links: {
PACKAGES_DOCS_PATH,
},
components: {
GlSprintf,
GlLink,
SettingsBlock,
MavenSettings,
GenericSettings,
DuplicatesSettings,
},
inject: ['defaultExpanded', 'groupPath'],
props: {
packageSettings: {
type: Object,
required: true,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
errors: {},
};
},
methods: {
async updateSettings(payload) {
this.errors = {};
try {
const { data } = await this.$apollo.mutate({
mutation: updateNamespacePackageSettings,
variables: {
input: {
namespacePath: this.groupPath,
...payload,
},
},
update: updateGroupPackageSettings(this.groupPath),
optimisticResponse: updateGroupPackagesSettingsOptimisticResponse({
...this.packageSettings,
...payload,
}),
});
if (data.updateNamespacePackageSettings?.errors?.length > 0) {
throw new Error();
} else {
this.$emit('success');
}
} catch (e) {
if (e.graphQLErrors) {
e.graphQLErrors.forEach((error) => {
const [
{
path: [key],
message,
},
] = error.extensions.problems;
this.errors = { ...this.errors, [key]: message };
});
}
this.$emit('error');
}
},
},
};
</script>
<template>
<settings-block
:default-expanded="defaultExpanded"
data-qa-selector="package_registry_settings_content"
>
<template #title> {{ $options.i18n.PACKAGE_SETTINGS_HEADER }}</template>
<template #description>
<span data-testid="description">
<gl-sprintf :message="$options.i18n.PACKAGE_SETTINGS_DESCRIPTION">
<template #link="{ content }">
<gl-link :href="$options.links.PACKAGES_DOCS_PATH" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</span>
</template>
<template #default>
<maven-settings data-testid="maven-settings">
<template #default="{ modelNames }">
<duplicates-settings
:duplicates-allowed="packageSettings.mavenDuplicatesAllowed"
:duplicate-exception-regex="packageSettings.mavenDuplicateExceptionRegex"
:duplicate-exception-regex-error="errors.mavenDuplicateExceptionRegex"
:model-names="modelNames"
:loading="isLoading"
toggle-qa-selector="allow_duplicates_toggle"
label-qa-selector="allow_duplicates_label"
@update="updateSettings"
/>
</template>
</maven-settings>
<generic-settings class="gl-mt-6" data-testid="generic-settings">
<template #default="{ modelNames }">
<duplicates-settings
:duplicates-allowed="packageSettings.genericDuplicatesAllowed"
:duplicate-exception-regex="packageSettings.genericDuplicateExceptionRegex"
:duplicate-exception-regex-error="errors.genericDuplicateExceptionRegex"
:model-names="modelNames"
:loading="isLoading"
@update="updateSettings"
/>
</template>
</generic-settings>
</template>
</settings-block>
</template>
...@@ -23,8 +23,15 @@ export const ERROR_UPDATING_SETTINGS = s__( ...@@ -23,8 +23,15 @@ export const ERROR_UPDATING_SETTINGS = s__(
'PackageRegistry|An error occurred while saving the settings', 'PackageRegistry|An error occurred while saving the settings',
); );
export const DEPENDENCY_PROXY_HEADER = s__('DependencyProxy|Dependency Proxy');
export const DEPENDENCY_PROXY_SETTINGS_DESCRIPTION = s__(
'DependencyProxy|Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies.',
);
// Parameters // Parameters
export const PACKAGES_DOCS_PATH = helpPagePath('user/packages'); export const PACKAGES_DOCS_PATH = helpPagePath('user/packages');
export const MAVEN_DUPLICATES_ALLOWED = 'mavenDuplicatesAllowed'; export const MAVEN_DUPLICATES_ALLOWED = 'mavenDuplicatesAllowed';
export const MAVEN_DUPLICATE_EXCEPTION_REGEX = 'mavenDuplicateExceptionRegex'; export const MAVEN_DUPLICATE_EXCEPTION_REGEX = 'mavenDuplicateExceptionRegex';
export const DEPENDENCY_PROXY_DOCS_PATH = helpPagePath('user/packages/dependency_proxy/index');
...@@ -9510,9 +9510,6 @@ msgstr "" ...@@ -9510,9 +9510,6 @@ msgstr ""
msgid "Create a Mattermost team for this group" msgid "Create a Mattermost team for this group"
msgstr "" msgstr ""
msgid "Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies."
msgstr ""
msgid "Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies." msgid "Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies."
msgstr "" msgstr ""
...@@ -11160,6 +11157,12 @@ msgstr "" ...@@ -11160,6 +11157,12 @@ msgstr ""
msgid "Dependency proxy image prefix" msgid "Dependency proxy image prefix"
msgstr "" msgstr ""
msgid "DependencyProxy|Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies."
msgstr ""
msgid "DependencyProxy|Dependency Proxy"
msgstr ""
msgid "DependencyProxy|Toggle Dependency Proxy" msgid "DependencyProxy|Toggle Dependency Proxy"
msgstr "" msgstr ""
......
...@@ -7,11 +7,11 @@ module QA ...@@ -7,11 +7,11 @@ module QA
class PackageRegistries < QA::Page::Base class PackageRegistries < QA::Page::Base
include ::QA::Page::Settings::Common include ::QA::Page::Settings::Common
view 'app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue' do view 'app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue' do
element :package_registry_settings_content element :package_registry_settings_content
end end
view 'app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue' do view 'app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue' do
element :allow_duplicates_toggle element :allow_duplicates_toggle
element :allow_duplicates_label element :allow_duplicates_label
end end
......
import { GlSprintf, GlLink, GlAlert } from '@gitlab/ui'; import { GlAlert } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { nextTick } from 'vue';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue'; import PackagesSettings from '~/packages_and_registries/settings/group/components/packages_settings.vue';
import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
import component from '~/packages_and_registries/settings/group/components/group_settings_app.vue'; import component from '~/packages_and_registries/settings/group/components/group_settings_app.vue';
import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
import { import {
PACKAGE_SETTINGS_HEADER,
PACKAGE_SETTINGS_DESCRIPTION,
PACKAGES_DOCS_PATH,
ERROR_UPDATING_SETTINGS, ERROR_UPDATING_SETTINGS,
SUCCESS_UPDATING_SETTINGS, SUCCESS_UPDATING_SETTINGS,
} from '~/packages_and_registries/settings/group/constants'; } 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 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 { groupPackageSettingsMock, packageSettings } from '../mock_data';
import {
groupPackageSettingsMock,
groupPackageSettingsMutationMock,
groupPackageSettingsMutationErrorMock,
} from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -39,35 +31,18 @@ describe('Group Settings App', () => { ...@@ -39,35 +31,18 @@ describe('Group Settings App', () => {
}; };
const mountComponent = ({ const mountComponent = ({
provide = defaultProvide,
resolver = jest.fn().mockResolvedValue(groupPackageSettingsMock), resolver = jest.fn().mockResolvedValue(groupPackageSettingsMock),
mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()),
data = {},
} = {}) => { } = {}) => {
localVue.use(VueApollo); localVue.use(VueApollo);
const requestHandlers = [ const requestHandlers = [[getGroupPackagesSettingsQuery, resolver]];
[getGroupPackagesSettingsQuery, resolver],
[updateNamespacePackageSettings, mutationResolver],
];
apolloProvider = createMockApollo(requestHandlers); apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMount(component, { wrapper = shallowMount(component, {
localVue, localVue,
apolloProvider, apolloProvider,
provide, provide: defaultProvide,
data() {
return {
...data,
};
},
stubs: {
GlSprintf,
SettingsBlock,
MavenSettings,
GenericSettings,
},
mocks: { mocks: {
$toast: { $toast: {
show, show,
...@@ -84,271 +59,73 @@ describe('Group Settings App', () => { ...@@ -84,271 +59,73 @@ describe('Group Settings App', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
const findDescription = () => wrapper.find('[data-testid="description"');
const findLink = () => wrapper.findComponent(GlLink);
const findAlert = () => wrapper.findComponent(GlAlert); const findAlert = () => wrapper.findComponent(GlAlert);
const findMavenSettings = () => wrapper.findComponent(MavenSettings); const findPackageSettings = () => wrapper.findComponent(PackagesSettings);
const findMavenDuplicatedSettings = () => findMavenSettings().findComponent(DuplicatesSettings);
const findGenericSettings = () => wrapper.findComponent(GenericSettings);
const findGenericDuplicatedSettings = () =>
findGenericSettings().findComponent(DuplicatesSettings);
const waitForApolloQueryAndRender = async () => { const waitForApolloQueryAndRender = async () => {
await waitForPromises(); await waitForPromises();
await wrapper.vm.$nextTick(); await nextTick();
};
const emitSettingsUpdate = (override) => {
findMavenDuplicatedSettings().vm.$emit('update', {
mavenDuplicateExceptionRegex: ')',
...override,
});
}; };
it('renders a settings block', () => { describe.each`
mountComponent(); finder | entityProp | entityValue
${findPackageSettings} | ${'packageSettings'} | ${packageSettings()}
expect(findSettingsBlock().exists()).toBe(true); `('settings blocks', ({ finder, entityProp, entityValue }) => {
}); beforeEach(() => {
it('passes the correct props to settings block', () => {
mountComponent();
expect(findSettingsBlock().props('defaultExpanded')).toBe(false);
});
it('has the correct header text', () => {
mountComponent();
expect(wrapper.text()).toContain(PACKAGE_SETTINGS_HEADER);
});
it('has the correct description text', () => {
mountComponent();
expect(findDescription().text()).toMatchInterpolatedText(PACKAGE_SETTINGS_DESCRIPTION);
});
it('has the correct link', () => {
mountComponent();
expect(findLink().attributes()).toMatchObject({
href: PACKAGES_DOCS_PATH,
target: '_blank',
});
expect(findLink().text()).toBe('Learn more.');
});
it('calls the graphql API with the proper variables', () => {
const resolver = jest.fn().mockResolvedValue(groupPackageSettingsMock);
mountComponent({ resolver });
expect(resolver).toHaveBeenCalledWith({
fullPath: defaultProvide.groupPath,
});
});
describe('maven settings', () => {
it('exists', () => {
mountComponent();
expect(findMavenSettings().exists()).toBe(true);
});
it('assigns duplication allowness and exception props', async () => {
mountComponent();
expect(findMavenDuplicatedSettings().props('loading')).toBe(true);
await waitForApolloQueryAndRender();
const {
mavenDuplicatesAllowed,
mavenDuplicateExceptionRegex,
} = groupPackageSettingsMock.data.group.packageSettings;
expect(findMavenDuplicatedSettings().props()).toMatchObject({
duplicatesAllowed: mavenDuplicatesAllowed,
duplicateExceptionRegex: mavenDuplicateExceptionRegex,
duplicateExceptionRegexError: '',
loading: false,
});
});
it('on update event calls the mutation', async () => {
const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock());
mountComponent({ mutationResolver });
await waitForApolloQueryAndRender();
emitSettingsUpdate();
expect(mutationResolver).toHaveBeenCalledWith({
input: { mavenDuplicateExceptionRegex: ')', namespacePath: 'foo_group_path' },
});
});
});
describe('generic settings', () => {
it('exists', () => {
mountComponent(); mountComponent();
return waitForApolloQueryAndRender();
expect(findGenericSettings().exists()).toBe(true);
}); });
it('assigns duplication allowness and exception props', async () => { it('renders the settings block', () => {
mountComponent(); expect(finder().exists()).toBe(true);
expect(findGenericDuplicatedSettings().props('loading')).toBe(true);
await waitForApolloQueryAndRender();
const {
genericDuplicatesAllowed,
genericDuplicateExceptionRegex,
} = groupPackageSettingsMock.data.group.packageSettings;
expect(findGenericDuplicatedSettings().props()).toMatchObject({
duplicatesAllowed: genericDuplicatesAllowed,
duplicateExceptionRegex: genericDuplicateExceptionRegex,
duplicateExceptionRegexError: '',
loading: false,
});
}); });
it('on update event calls the mutation', async () => { it('binds the correctProps', () => {
const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()); expect(finder().props()).toMatchObject({
mountComponent({ mutationResolver }); isLoading: false,
[entityProp]: entityValue,
await waitForApolloQueryAndRender();
findMavenDuplicatedSettings().vm.$emit('update', {
genericDuplicateExceptionRegex: ')',
});
expect(mutationResolver).toHaveBeenCalledWith({
input: { genericDuplicateExceptionRegex: ')', namespacePath: 'foo_group_path' },
}); });
}); });
});
describe('settings update', () => {
describe('success state', () => {
it('shows a success alert', async () => {
mountComponent();
await waitForApolloQueryAndRender();
emitSettingsUpdate();
await waitForPromises();
describe('success event', () => {
it('shows a success toast', () => {
finder().vm.$emit('success');
expect(show).toHaveBeenCalledWith(SUCCESS_UPDATING_SETTINGS); expect(show).toHaveBeenCalledWith(SUCCESS_UPDATING_SETTINGS);
}); });
it('has an optimistic response', async () => { it('hides the error alert', async () => {
const mavenDuplicateExceptionRegex = 'latest[main]something'; finder().vm.$emit('error');
mountComponent(); await nextTick();
await waitForApolloQueryAndRender();
expect(findMavenDuplicatedSettings().props('duplicateExceptionRegex')).toBe('');
emitSettingsUpdate({ mavenDuplicateExceptionRegex });
// wait for apollo to update the model with the optimistic response
await wrapper.vm.$nextTick();
expect(findMavenDuplicatedSettings().props('duplicateExceptionRegex')).toBe(
mavenDuplicateExceptionRegex,
);
// wait for the call to resolve
await waitForPromises();
expect(findMavenDuplicatedSettings().props('duplicateExceptionRegex')).toBe(
mavenDuplicateExceptionRegex,
);
});
});
describe('errors', () => {
const verifyAlert = () => {
expect(findAlert().exists()).toBe(true); expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(ERROR_UPDATING_SETTINGS);
expect(findAlert().props('variant')).toBe('warning');
};
it('mutation payload with root level errors', async () => {
// note this is a complex test that covers all the path around errors that are shown in the form
// it's one single it case, due to the expensive preparation and execution
const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationErrorMock);
mountComponent({ mutationResolver });
await waitForApolloQueryAndRender();
emitSettingsUpdate();
await waitForApolloQueryAndRender();
// errors are bound to the component
expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe(
groupPackageSettingsMutationErrorMock.errors[0].extensions.problems[0].message,
);
// general error message is shown finder().vm.$emit('success');
await nextTick();
verifyAlert(); expect(findAlert().exists()).toBe(false);
emitSettingsUpdate();
await wrapper.vm.$nextTick();
// errors are reset on mutation call
expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe('');
}); });
});
it.each` describe('error event', () => {
type | mutationResolver beforeEach(() => {
${'local'} | ${jest.fn().mockResolvedValue(groupPackageSettingsMutationMock({ errors: ['foo'] }))} finder().vm.$emit('error');
${'network'} | ${jest.fn().mockRejectedValue()} return nextTick();
`('mutation payload with $type error', async ({ mutationResolver }) => {
mountComponent({ mutationResolver });
await waitForApolloQueryAndRender();
emitSettingsUpdate();
await waitForPromises();
verifyAlert();
}); });
it('a successful request dismisses the alert', async () => { it('shows an alert', () => {
mountComponent({ data: { alertMessage: 'foo' } });
await waitForApolloQueryAndRender();
expect(findAlert().exists()).toBe(true); expect(findAlert().exists()).toBe(true);
emitSettingsUpdate();
await waitForPromises();
expect(findAlert().exists()).toBe(false);
}); });
it('dismiss event from alert dismiss it from the page', async () => { it('alert has the right text', () => {
mountComponent({ data: { alertMessage: 'foo' } }); expect(findAlert().text()).toBe(ERROR_UPDATING_SETTINGS);
});
await waitForApolloQueryAndRender();
it('dismissing the alert removes it', async () => {
expect(findAlert().exists()).toBe(true); expect(findAlert().exists()).toBe(true);
findAlert().vm.$emit('dismiss'); findAlert().vm.$emit('dismiss');
await wrapper.vm.$nextTick(); await nextTick();
expect(findAlert().exists()).toBe(false); expect(findAlert().exists()).toBe(false);
}); });
......
import { GlSprintf, GlLink } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
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 DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
import component from '~/packages_and_registries/settings/group/components/packages_settings.vue';
import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
import {
PACKAGE_SETTINGS_HEADER,
PACKAGE_SETTINGS_DESCRIPTION,
PACKAGES_DOCS_PATH,
} 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,
groupPackageSettingsMock,
groupPackageSettingsMutationMock,
groupPackageSettingsMutationErrorMock,
} from '../mock_data';
jest.mock('~/flash');
jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses');
const localVue = createLocalVue();
describe('Packages Settings', () => {
let wrapper;
let apolloProvider;
const defaultProvide = {
defaultExpanded: false,
groupPath: 'foo_group_path',
};
const mountComponent = ({
mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()),
} = {}) => {
localVue.use(VueApollo);
const requestHandlers = [[updateNamespacePackageSettings, mutationResolver]];
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(component, {
localVue,
apolloProvider,
provide: defaultProvide,
propsData: {
packageSettings: packageSettings(),
},
stubs: {
GlSprintf,
SettingsBlock,
MavenSettings,
GenericSettings,
},
});
};
afterEach(() => {
wrapper.destroy();
});
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
const findDescription = () => wrapper.findByTestId('description');
const findLink = () => wrapper.findComponent(GlLink);
const findMavenSettings = () => wrapper.findComponent(MavenSettings);
const findMavenDuplicatedSettings = () => findMavenSettings().findComponent(DuplicatesSettings);
const findGenericSettings = () => wrapper.findComponent(GenericSettings);
const findGenericDuplicatedSettings = () =>
findGenericSettings().findComponent(DuplicatesSettings);
const fillApolloCache = () => {
apolloProvider.defaultClient.cache.writeQuery({
query: getGroupPackagesSettingsQuery,
variables: {
fullPath: defaultProvide.groupPath,
},
...groupPackageSettingsMock,
});
};
const emitMavenSettingsUpdate = (override) => {
findMavenDuplicatedSettings().vm.$emit('update', {
mavenDuplicateExceptionRegex: ')',
...override,
});
};
it('renders a settings block', () => {
mountComponent();
expect(findSettingsBlock().exists()).toBe(true);
});
it('passes the correct props to settings block', () => {
mountComponent();
expect(findSettingsBlock().props('defaultExpanded')).toBe(false);
});
it('has the correct header text', () => {
mountComponent();
expect(wrapper.text()).toContain(PACKAGE_SETTINGS_HEADER);
});
it('has the correct description text', () => {
mountComponent();
expect(findDescription().text()).toMatchInterpolatedText(PACKAGE_SETTINGS_DESCRIPTION);
});
it('has the correct link', () => {
mountComponent();
expect(findLink().attributes()).toMatchObject({
href: PACKAGES_DOCS_PATH,
target: '_blank',
});
expect(findLink().text()).toBe('Learn more.');
});
describe('maven settings', () => {
it('exists', () => {
mountComponent();
expect(findMavenSettings().exists()).toBe(true);
});
it('assigns duplication allowness and exception props', async () => {
mountComponent();
const { mavenDuplicatesAllowed, mavenDuplicateExceptionRegex } = packageSettings();
expect(findMavenDuplicatedSettings().props()).toMatchObject({
duplicatesAllowed: mavenDuplicatesAllowed,
duplicateExceptionRegex: mavenDuplicateExceptionRegex,
duplicateExceptionRegexError: '',
loading: false,
});
});
it('on update event calls the mutation', () => {
const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock());
mountComponent({ mutationResolver });
fillApolloCache();
emitMavenSettingsUpdate();
expect(mutationResolver).toHaveBeenCalledWith({
input: { mavenDuplicateExceptionRegex: ')', namespacePath: 'foo_group_path' },
});
});
});
describe('generic settings', () => {
it('exists', () => {
mountComponent();
expect(findGenericSettings().exists()).toBe(true);
});
it('assigns duplication allowness and exception props', async () => {
mountComponent();
const { genericDuplicatesAllowed, genericDuplicateExceptionRegex } = packageSettings();
expect(findGenericDuplicatedSettings().props()).toMatchObject({
duplicatesAllowed: genericDuplicatesAllowed,
duplicateExceptionRegex: genericDuplicateExceptionRegex,
duplicateExceptionRegexError: '',
loading: false,
});
});
it('on update event calls the mutation', async () => {
const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock());
mountComponent({ mutationResolver });
fillApolloCache();
findMavenDuplicatedSettings().vm.$emit('update', {
genericDuplicateExceptionRegex: ')',
});
expect(mutationResolver).toHaveBeenCalledWith({
input: { genericDuplicateExceptionRegex: ')', namespacePath: 'foo_group_path' },
});
});
});
describe('settings update', () => {
describe('success state', () => {
it('emits a success event', async () => {
mountComponent();
fillApolloCache();
emitMavenSettingsUpdate();
await waitForPromises();
expect(wrapper.emitted('success')).toEqual([[]]);
});
it('has an optimistic response', () => {
const mavenDuplicateExceptionRegex = 'latest[main]something';
mountComponent();
fillApolloCache();
expect(findMavenDuplicatedSettings().props('duplicateExceptionRegex')).toBe('');
emitMavenSettingsUpdate({ mavenDuplicateExceptionRegex });
expect(updateGroupPackagesSettingsOptimisticResponse).toHaveBeenCalledWith({
...packageSettings(),
mavenDuplicateExceptionRegex,
});
});
});
describe('errors', () => {
it('mutation payload with root level errors', async () => {
// note this is a complex test that covers all the path around errors that are shown in the form
// it's one single it case, due to the expensive preparation and execution
const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationErrorMock);
mountComponent({ mutationResolver });
fillApolloCache();
emitMavenSettingsUpdate();
await waitForPromises();
// errors are bound to the component
expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe(
groupPackageSettingsMutationErrorMock.errors[0].extensions.problems[0].message,
);
// general error message is shown
expect(wrapper.emitted('error')).toEqual([[]]);
emitMavenSettingsUpdate();
await wrapper.vm.$nextTick();
// errors are reset on mutation call
expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe('');
});
it.each`
type | mutationResolver
${'local'} | ${jest.fn().mockResolvedValue(groupPackageSettingsMutationMock({ errors: ['foo'] }))}
${'network'} | ${jest.fn().mockRejectedValue()}
`('mutation payload with $type error', async ({ mutationResolver }) => {
mountComponent({ mutationResolver });
fillApolloCache();
emitMavenSettingsUpdate();
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[]]);
});
});
});
});
export const packageSettings = () => ({
mavenDuplicatesAllowed: true,
mavenDuplicateExceptionRegex: '',
genericDuplicatesAllowed: true,
genericDuplicateExceptionRegex: '',
});
export const groupPackageSettingsMock = { export const groupPackageSettingsMock = {
data: { data: {
group: { group: {
packageSettings: { fullPath: 'foo_group_path',
mavenDuplicatesAllowed: true, packageSettings: packageSettings(),
mavenDuplicateExceptionRegex: '',
genericDuplicatesAllowed: true,
genericDuplicateExceptionRegex: '',
},
}, },
}, },
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment