diff options
Diffstat (limited to 'spec/frontend/packages_and_registries')
5 files changed, 586 insertions, 0 deletions
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 new file mode 100644 index 00000000000..be0d7114e6e --- /dev/null +++ b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js @@ -0,0 +1,309 @@ +import { GlSprintf, GlLink, GlAlert } from '@gitlab/ui'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +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 { + PACKAGE_SETTINGS_HEADER, + PACKAGE_SETTINGS_DESCRIPTION, + PACKAGES_DOCS_PATH, + ERROR_UPDATING_SETTINGS, + SUCCESS_UPDATING_SETTINGS, +} 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 { + groupPackageSettingsMock, + groupPackageSettingsMutationMock, + groupPackageSettingsMutationErrorMock, +} from '../mock_data'; + +jest.mock('~/flash'); + +const localVue = createLocalVue(); + +describe('Group Settings App', () => { + let wrapper; + let apolloProvider; + let show; + + const defaultProvide = { + defaultExpanded: false, + groupPath: 'foo_group_path', + }; + + const mountComponent = ({ + provide = defaultProvide, + resolver = jest.fn().mockResolvedValue(groupPackageSettingsMock), + mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()), + data = {}, + } = {}) => { + localVue.use(VueApollo); + + const requestHandlers = [ + [getGroupPackagesSettingsQuery, resolver], + [updateNamespacePackageSettings, mutationResolver], + ]; + + apolloProvider = createMockApollo(requestHandlers); + + wrapper = shallowMount(component, { + localVue, + apolloProvider, + provide, + data() { + return { + ...data, + }; + }, + stubs: { + GlSprintf, + SettingsBlock, + }, + mocks: { + $toast: { + show, + }, + }, + }); + }; + + beforeEach(() => { + show = jest.fn(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findSettingsBlock = () => wrapper.find(SettingsBlock); + const findDescription = () => wrapper.find('[data-testid="description"'); + const findLink = () => wrapper.find(GlLink); + const findMavenSettings = () => wrapper.find(MavenSettings); + const findAlert = () => wrapper.find(GlAlert); + + const waitForApolloQueryAndRender = async () => { + await waitForPromises(); + await wrapper.vm.$nextTick(); + }; + + const emitSettingsUpdate = (override) => { + findMavenSettings().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('More Information'); + }); + + 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(findMavenSettings().props('loading')).toBe(true); + + await waitForApolloQueryAndRender(); + + const { + mavenDuplicatesAllowed, + mavenDuplicateExceptionRegex, + } = groupPackageSettingsMock.data.group.packageSettings; + + expect(findMavenSettings().props()).toMatchObject({ + mavenDuplicatesAllowed, + mavenDuplicateExceptionRegex, + mavenDuplicateExceptionRegexError: '', + 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('settings update', () => { + describe('success state', () => { + it('shows a success alert', async () => { + mountComponent(); + + await waitForApolloQueryAndRender(); + + emitSettingsUpdate(); + + await waitForPromises(); + + expect(show).toHaveBeenCalledWith(SUCCESS_UPDATING_SETTINGS, { + type: 'success', + }); + }); + + it('has an optimistic response', async () => { + const mavenDuplicateExceptionRegex = 'latest[master]something'; + mountComponent(); + + await waitForApolloQueryAndRender(); + + expect(findMavenSettings().props('mavenDuplicateExceptionRegex')).toBe(''); + + emitSettingsUpdate({ mavenDuplicateExceptionRegex }); + + // wait for apollo to update the model with the optimistic response + await wrapper.vm.$nextTick(); + + expect(findMavenSettings().props('mavenDuplicateExceptionRegex')).toBe( + mavenDuplicateExceptionRegex, + ); + + // wait for the call to resolve + await waitForPromises(); + + expect(findMavenSettings().props('mavenDuplicateExceptionRegex')).toBe( + mavenDuplicateExceptionRegex, + ); + }); + }); + + describe('errors', () => { + const verifyAlert = () => { + 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(findMavenSettings().props('mavenDuplicateExceptionRegexError')).toBe( + groupPackageSettingsMutationErrorMock.errors[0].extensions.problems[0].message, + ); + + // general error message is shown + + verifyAlert(); + + emitSettingsUpdate(); + + await wrapper.vm.$nextTick(); + + // errors are reset on mutation call + expect(findMavenSettings().props('mavenDuplicateExceptionRegexError')).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 }); + + await waitForApolloQueryAndRender(); + + emitSettingsUpdate(); + + await waitForPromises(); + + verifyAlert(); + }); + + it('a successful request dismisses the alert', async () => { + mountComponent({ data: { alertMessage: 'foo' } }); + + await waitForApolloQueryAndRender(); + + expect(findAlert().exists()).toBe(true); + + emitSettingsUpdate(); + + await waitForPromises(); + + expect(findAlert().exists()).toBe(false); + }); + + it('dismiss event from alert dismiss it from the page', async () => { + mountComponent({ data: { alertMessage: 'foo' } }); + + await waitForApolloQueryAndRender(); + + expect(findAlert().exists()).toBe(true); + + findAlert().vm.$emit('dismiss'); + + await wrapper.vm.$nextTick(); + + expect(findAlert().exists()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js new file mode 100644 index 00000000000..2433c50ff24 --- /dev/null +++ b/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js @@ -0,0 +1,153 @@ +import { GlSprintf, GlToggle, GlFormGroup, GlFormInput } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import component from '~/packages_and_registries/settings/group/components/maven_settings.vue'; + +import { + MAVEN_TITLE, + MAVEN_SETTINGS_SUBTITLE, + MAVEN_DUPLICATES_ALLOWED_DISABLED, + MAVEN_DUPLICATES_ALLOWED_ENABLED, + MAVEN_SETTING_EXCEPTION_TITLE, + MAVEN_SETTINGS_EXCEPTION_LEGEND, +} from '~/packages_and_registries/settings/group/constants'; + +describe('Maven Settings', () => { + let wrapper; + + const defaultProps = { + mavenDuplicatesAllowed: false, + mavenDuplicateExceptionRegex: 'foo', + }; + + const mountComponent = (propsData = defaultProps) => { + wrapper = shallowMount(component, { + propsData, + stubs: { + GlSprintf, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findTitle = () => wrapper.find('h5'); + const findSubTitle = () => wrapper.find('p'); + const findToggle = () => wrapper.find(GlToggle); + const findToggleLabel = () => wrapper.find('[data-testid="toggle-label"'); + + const findInputGroup = () => wrapper.find(GlFormGroup); + const findInput = () => wrapper.find(GlFormInput); + + it('has a title', () => { + mountComponent(); + + expect(findTitle().exists()).toBe(true); + expect(findTitle().text()).toBe(MAVEN_TITLE); + }); + + it('has a subtitle', () => { + mountComponent(); + + expect(findSubTitle().exists()).toBe(true); + expect(findSubTitle().text()).toBe(MAVEN_SETTINGS_SUBTITLE); + }); + + it('has a toggle', () => { + mountComponent(); + + expect(findToggle().exists()).toBe(true); + expect(findToggle().props('value')).toBe(defaultProps.mavenDuplicatesAllowed); + }); + + it('toggle emits an update event', () => { + mountComponent(); + + findToggle().vm.$emit('change', false); + + expect(wrapper.emitted('update')).toEqual([[{ mavenDuplicatesAllowed: false }]]); + }); + + describe('when the duplicates are disabled', () => { + it('the toggle has the disabled message', () => { + mountComponent(); + + expect(findToggleLabel().exists()).toBe(true); + expect(findToggleLabel().text()).toMatchInterpolatedText(MAVEN_DUPLICATES_ALLOWED_DISABLED); + }); + + it('shows a form group with an input field', () => { + mountComponent(); + + expect(findInputGroup().exists()).toBe(true); + + expect(findInputGroup().attributes()).toMatchObject({ + 'label-for': 'maven-duplicated-settings-regex-input', + label: MAVEN_SETTING_EXCEPTION_TITLE, + description: MAVEN_SETTINGS_EXCEPTION_LEGEND, + }); + }); + + it('shows an input field', () => { + mountComponent(); + + expect(findInput().exists()).toBe(true); + + expect(findInput().attributes()).toMatchObject({ + id: 'maven-duplicated-settings-regex-input', + value: defaultProps.mavenDuplicateExceptionRegex, + }); + }); + + it('input change event emits an update event', () => { + mountComponent(); + + findInput().vm.$emit('change', 'bar'); + + expect(wrapper.emitted('update')).toEqual([[{ mavenDuplicateExceptionRegex: 'bar' }]]); + }); + + describe('valid state', () => { + it('form group has correct props', () => { + mountComponent(); + + expect(findInputGroup().attributes()).toMatchObject({ + state: 'true', + 'invalid-feedback': '', + }); + }); + }); + + describe('invalid state', () => { + it('form group has correct props', () => { + const propsWithError = { + ...defaultProps, + mavenDuplicateExceptionRegexError: 'some error string', + }; + + mountComponent(propsWithError); + + expect(findInputGroup().attributes()).toMatchObject({ + 'invalid-feedback': propsWithError.mavenDuplicateExceptionRegexError, + }); + }); + }); + }); + + describe('when the duplicates are enabled', () => { + it('has the correct toggle label', () => { + mountComponent({ ...defaultProps, mavenDuplicatesAllowed: true }); + + expect(findToggleLabel().exists()).toBe(true); + expect(findToggleLabel().text()).toMatchInterpolatedText(MAVEN_DUPLICATES_ALLOWED_ENABLED); + }); + + it('hides the form input group', () => { + mountComponent({ ...defaultProps, mavenDuplicatesAllowed: true }); + + expect(findInputGroup().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js b/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js new file mode 100644 index 00000000000..e1a46f97318 --- /dev/null +++ b/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js @@ -0,0 +1,56 @@ +import expirationPolicyQuery 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'; + +describe('Package and Registries settings group cache updates', () => { + let client; + + const payload = { + data: { + updateNamespacePackageSettings: { + packageSettings: { + mavenDuplicatesAllowed: false, + mavenDuplicateExceptionRegex: 'latest[master]something', + }, + }, + }, + }; + + const cacheMock = { + group: { + packageSettings: { + mavenDuplicatesAllowed: true, + mavenDuplicateExceptionRegex: '', + }, + }, + }; + + const queryAndVariables = { + query: expirationPolicyQuery, + variables: { fullPath: 'foo' }, + }; + + beforeEach(() => { + client = { + readQuery: jest.fn().mockReturnValue(cacheMock), + writeQuery: jest.fn(), + }; + }); + describe('updateGroupPackageSettings', () => { + it('calls readQuery', () => { + updateGroupPackageSettings('foo')(client, payload); + expect(client.readQuery).toHaveBeenCalledWith(queryAndVariables); + }); + + it('writes the correct result in the cache', () => { + updateGroupPackageSettings('foo')(client, payload); + expect(client.writeQuery).toHaveBeenCalledWith({ + ...queryAndVariables, + data: { + group: { + ...payload.data.updateNamespacePackageSettings, + }, + }, + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js b/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js new file mode 100644 index 00000000000..a3c53d5768a --- /dev/null +++ b/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js @@ -0,0 +1,20 @@ +import { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses'; + +describe('Optimistic responses', () => { + describe('updateGroupPackagesSettingsOptimisticResponse', () => { + it('returns the correct structure', () => { + expect(updateGroupPackagesSettingsOptimisticResponse({ foo: 'bar' })).toMatchInlineSnapshot(` + Object { + "__typename": "Mutation", + "updateNamespacePackageSettings": Object { + "__typename": "UpdateNamespacePackageSettingsPayload", + "errors": Array [], + "packageSettings": Object { + "foo": "bar", + }, + }, + } + `); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/settings/group/mock_data.js b/spec/frontend/packages_and_registries/settings/group/mock_data.js new file mode 100644 index 00000000000..777c0898de0 --- /dev/null +++ b/spec/frontend/packages_and_registries/settings/group/mock_data.js @@ -0,0 +1,48 @@ +export const groupPackageSettingsMock = { + data: { + group: { + packageSettings: { + mavenDuplicatesAllowed: true, + mavenDuplicateExceptionRegex: '', + }, + }, + }, +}; + +export const groupPackageSettingsMutationMock = (override) => ({ + data: { + updateNamespacePackageSettings: { + packageSettings: { + mavenDuplicatesAllowed: true, + mavenDuplicateExceptionRegex: 'latest[master]something', + }, + errors: [], + ...override, + }, + }, +}); + +export const groupPackageSettingsMutationErrorMock = { + errors: [ + { + message: + 'Variable $input of type UpdateNamespacePackageSettingsInput! was provided invalid value for mavenDuplicateExceptionRegex (latest[master]somethingj)) is an invalid regexp: unexpected ): latest[master]somethingj)))', + locations: [{ line: 1, column: 41 }], + extensions: { + value: { + namespacePath: 'gitlab-org', + mavenDuplicateExceptionRegex: 'latest[master]something))', + }, + problems: [ + { + path: ['mavenDuplicateExceptionRegex'], + explanation: + 'latest[master]somethingj)) is an invalid regexp: unexpected ): latest[master]something))', + message: + 'latest[master]somethingj)) is an invalid regexp: unexpected ): latest[master]something))', + }, + ], + }, + }, + ], +}; |