diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 12:08:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 12:08:42 +0300 |
commit | b76ae638462ab0f673e5915986070518dd3f9ad3 (patch) | |
tree | bdab0533383b52873be0ec0eb4d3c66598ff8b91 /spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js | |
parent | 434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff) |
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js')
-rw-r--r-- | spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js | 448 |
1 files changed, 432 insertions, 16 deletions
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js index 97444ec108f..5119512564f 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js @@ -1,35 +1,451 @@ -import { GlEmptyState } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { GlEmptyState, GlBadge, GlTabs, GlTab } from '@gitlab/ui'; +import { createLocalVue } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; +import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue'; import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue'; +import DependencyRow from '~/packages_and_registries/package_registry/components/details/dependency_row.vue'; +import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue'; +import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue'; +import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue'; +import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue'; +import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue'; +import { + FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, + DELETE_PACKAGE_ERROR_MESSAGE, + PACKAGE_TYPE_COMPOSER, + DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, + DELETE_PACKAGE_FILE_ERROR_MESSAGE, + PACKAGE_TYPE_NUGET, +} from '~/packages_and_registries/package_registry/constants'; + +import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql'; +import destroyPackageFileMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql'; +import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql'; +import { + packageDetailsQuery, + packageData, + packageVersions, + dependencyLinks, + emptyPackageDetailsQuery, + packageDestroyMutation, + packageDestroyMutationError, + packageFiles, + packageDestroyFileMutation, + packageDestroyFileMutationError, +} from '../../mock_data'; + +jest.mock('~/flash'); +useMockLocationHelper(); + +const localVue = createLocalVue(); describe('PackagesApp', () => { let wrapper; + let apolloProvider; + + const provide = { + packageId: '111', + titleComponent: 'PackageTitle', + projectName: 'projectName', + canDelete: 'canDelete', + svgPath: 'svgPath', + npmPath: 'npmPath', + npmHelpPath: 'npmHelpPath', + projectListUrl: 'projectListUrl', + groupListUrl: 'groupListUrl', + }; - function createComponent() { - wrapper = shallowMount(PackagesApp, { - provide: { - titleComponent: 'titleComponent', - projectName: 'projectName', - canDelete: 'canDelete', - svgPath: 'svgPath', - npmPath: 'npmPath', - npmHelpPath: 'npmHelpPath', - projectListUrl: 'projectListUrl', - groupListUrl: 'groupListUrl', + function createComponent({ + resolver = jest.fn().mockResolvedValue(packageDetailsQuery()), + mutationResolver = jest.fn().mockResolvedValue(packageDestroyMutation()), + fileDeleteMutationResolver = jest.fn().mockResolvedValue(packageDestroyFileMutation()), + } = {}) { + localVue.use(VueApollo); + + const requestHandlers = [ + [getPackageDetails, resolver], + [destroyPackageMutation, mutationResolver], + [destroyPackageFileMutation, fileDeleteMutationResolver], + ]; + apolloProvider = createMockApollo(requestHandlers); + + wrapper = shallowMountExtended(PackagesApp, { + localVue, + apolloProvider, + provide, + stubs: { + PackageTitle, + GlModal: { + template: '<div></div>', + methods: { + show: jest.fn(), + }, + }, + GlTabs, + GlTab, }, }); } - const emptyState = () => wrapper.findComponent(GlEmptyState); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + const findPackageTitle = () => wrapper.findComponent(PackageTitle); + const findPackageHistory = () => wrapper.findComponent(PackageHistory); + const findAdditionalMetadata = () => wrapper.findComponent(AdditionalMetadata); + const findInstallationCommands = () => wrapper.findComponent(InstallationCommands); + const findDeleteModal = () => wrapper.findByTestId('delete-modal'); + const findDeleteButton = () => wrapper.findByTestId('delete-package'); + const findPackageFiles = () => wrapper.findComponent(PackageFiles); + const findDeleteFileModal = () => wrapper.findByTestId('delete-file-modal'); + const findVersionRows = () => wrapper.findAllComponents(VersionRow); + const noVersionsMessage = () => wrapper.findByTestId('no-versions-message'); + const findDependenciesCountBadge = () => wrapper.findComponent(GlBadge); + const findNoDependenciesMessage = () => wrapper.findByTestId('no-dependencies-message'); + const findDependencyRows = () => wrapper.findAllComponents(DependencyRow); afterEach(() => { wrapper.destroy(); }); - it('renders an empty state component', () => { + it('renders an empty state component', async () => { + createComponent({ resolver: jest.fn().mockResolvedValue(emptyPackageDetailsQuery) }); + + await waitForPromises(); + + expect(findEmptyState().exists()).toBe(true); + }); + + it('renders the app and displays the package title', async () => { + createComponent(); + + await waitForPromises(); + + expect(findPackageTitle().exists()).toBe(true); + expect(findPackageTitle().props()).toMatchObject({ + packageEntity: expect.objectContaining(packageData()), + }); + }); + + it('emits an error message if the load fails', async () => { + createComponent({ resolver: jest.fn().mockRejectedValue() }); + + await waitForPromises(); + + expect(createFlash).toHaveBeenCalledWith( + expect.objectContaining({ + message: FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, + }), + ); + }); + + it('renders history and has the right props', async () => { + createComponent(); + + await waitForPromises(); + + expect(findPackageHistory().exists()).toBe(true); + expect(findPackageHistory().props()).toMatchObject({ + packageEntity: expect.objectContaining(packageData()), + projectName: provide.projectName, + }); + }); + + it('renders additional metadata and has the right props', async () => { + createComponent(); + + await waitForPromises(); + + expect(findAdditionalMetadata().exists()).toBe(true); + expect(findAdditionalMetadata().props()).toMatchObject({ + packageEntity: expect.objectContaining(packageData()), + }); + }); + + it('renders installation commands and has the right props', async () => { createComponent(); - expect(emptyState().exists()).toBe(true); + await waitForPromises(); + + expect(findInstallationCommands().exists()).toBe(true); + expect(findInstallationCommands().props()).toMatchObject({ + packageEntity: expect.objectContaining(packageData()), + }); + }); + + describe('delete package', () => { + const originalReferrer = document.referrer; + const setReferrer = (value = provide.projectName) => { + Object.defineProperty(document, 'referrer', { + value, + configurable: true, + }); + }; + + const performDeletePackage = async () => { + await findDeleteButton().trigger('click'); + + findDeleteModal().vm.$emit('primary'); + + await waitForPromises(); + }; + + afterEach(() => { + Object.defineProperty(document, 'referrer', { + value: originalReferrer, + configurable: true, + }); + }); + + it('shows the delete confirmation modal when delete is clicked', async () => { + createComponent(); + + await waitForPromises(); + + await findDeleteButton().trigger('click'); + + expect(findDeleteModal().exists()).toBe(true); + }); + + describe('successful request', () => { + it('when referrer contains project name calls window.replace with project url', async () => { + setReferrer(); + + createComponent(); + + await waitForPromises(); + + await performDeletePackage(); + + expect(window.location.replace).toHaveBeenCalledWith( + 'projectListUrl?showSuccessDeleteAlert=true', + ); + }); + + it('when referrer does not contain project name calls window.replace with group url', async () => { + setReferrer('baz'); + + createComponent(); + + await waitForPromises(); + + await performDeletePackage(); + + expect(window.location.replace).toHaveBeenCalledWith( + 'groupListUrl?showSuccessDeleteAlert=true', + ); + }); + }); + + describe('request failure', () => { + it('on global failure it displays an alert', async () => { + createComponent({ mutationResolver: jest.fn().mockRejectedValue() }); + + await waitForPromises(); + + await performDeletePackage(); + + expect(createFlash).toHaveBeenCalledWith( + expect.objectContaining({ + message: DELETE_PACKAGE_ERROR_MESSAGE, + }), + ); + }); + + it('on payload with error it displays an alert', async () => { + createComponent({ + mutationResolver: jest.fn().mockResolvedValue(packageDestroyMutationError()), + }); + + await waitForPromises(); + + await performDeletePackage(); + + expect(createFlash).toHaveBeenCalledWith( + expect.objectContaining({ + message: DELETE_PACKAGE_ERROR_MESSAGE, + }), + ); + }); + }); + }); + + describe('package files', () => { + it('renders the package files component and has the right props', async () => { + const expectedFile = { ...packageFiles()[0] }; + // eslint-disable-next-line no-underscore-dangle + delete expectedFile.__typename; + createComponent(); + + await waitForPromises(); + + expect(findPackageFiles().exists()).toBe(true); + + expect(findPackageFiles().props('packageFiles')[0]).toMatchObject(expectedFile); + }); + + it('does not render the package files table when the package is composer', async () => { + createComponent({ + resolver: jest + .fn() + .mockResolvedValue(packageDetailsQuery({ packageType: PACKAGE_TYPE_COMPOSER })), + }); + + await waitForPromises(); + + expect(findPackageFiles().exists()).toBe(false); + }); + + describe('deleting a file', () => { + const [fileToDelete] = packageFiles(); + + const doDeleteFile = () => { + findPackageFiles().vm.$emit('delete-file', fileToDelete); + + findDeleteFileModal().vm.$emit('primary'); + + return waitForPromises(); + }; + + it('opens a confirmation modal', async () => { + createComponent(); + + await waitForPromises(); + + findPackageFiles().vm.$emit('delete-file', fileToDelete); + + await nextTick(); + + expect(findDeleteFileModal().exists()).toBe(true); + }); + + it('confirming on the modal deletes the file and shows a success message', async () => { + const resolver = jest.fn().mockResolvedValue(packageDetailsQuery()); + createComponent({ resolver }); + + await waitForPromises(); + + await doDeleteFile(); + + expect(createFlash).toHaveBeenCalledWith( + expect.objectContaining({ + message: DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, + }), + ); + // we are re-fetching the package details, so we expect the resolver to have been called twice + expect(resolver).toHaveBeenCalledTimes(2); + }); + + describe('errors', () => { + it('shows an error when the mutation request fails', async () => { + createComponent({ fileDeleteMutationResolver: jest.fn().mockRejectedValue() }); + await waitForPromises(); + + await doDeleteFile(); + + expect(createFlash).toHaveBeenCalledWith( + expect.objectContaining({ + message: DELETE_PACKAGE_FILE_ERROR_MESSAGE, + }), + ); + }); + + it('shows an error when the mutation request returns an error payload', async () => { + createComponent({ + fileDeleteMutationResolver: jest + .fn() + .mockResolvedValue(packageDestroyFileMutationError()), + }); + await waitForPromises(); + + await doDeleteFile(); + + expect(createFlash).toHaveBeenCalledWith( + expect.objectContaining({ + message: DELETE_PACKAGE_FILE_ERROR_MESSAGE, + }), + ); + }); + }); + }); + }); + + describe('versions', () => { + it('displays the correct version count when the package has versions', async () => { + createComponent(); + + await waitForPromises(); + + expect(findVersionRows()).toHaveLength(packageVersions().length); + }); + + it('binds the correct props', async () => { + const [versionPackage] = packageVersions(); + // eslint-disable-next-line no-underscore-dangle + delete versionPackage.__typename; + delete versionPackage.tags; + + createComponent(); + + await waitForPromises(); + + expect(findVersionRows().at(0).props()).toMatchObject({ + packageEntity: expect.objectContaining(versionPackage), + }); + }); + + it('displays the no versions message when there are none', async () => { + createComponent({ + resolver: jest.fn().mockResolvedValue(packageDetailsQuery({ versions: { nodes: [] } })), + }); + + await waitForPromises(); + + expect(noVersionsMessage().exists()).toBe(true); + }); + }); + describe('dependency links', () => { + it('does not show the dependency links for a non nuget package', async () => { + createComponent(); + + expect(findDependenciesCountBadge().exists()).toBe(false); + }); + + it('shows the dependencies tab with 0 count when a nuget package with no dependencies', async () => { + createComponent({ + resolver: jest.fn().mockResolvedValue( + packageDetailsQuery({ + packageType: PACKAGE_TYPE_NUGET, + dependencyLinks: { nodes: [] }, + }), + ), + }); + + await waitForPromises(); + + expect(findDependenciesCountBadge().exists()).toBe(true); + expect(findDependenciesCountBadge().text()).toBe('0'); + expect(findNoDependenciesMessage().exists()).toBe(true); + }); + + it('renders the correct number of dependency rows for a nuget package', async () => { + createComponent({ + resolver: jest.fn().mockResolvedValue( + packageDetailsQuery({ + packageType: PACKAGE_TYPE_NUGET, + }), + ), + }); + await waitForPromises(); + + expect(findDependenciesCountBadge().exists()).toBe(true); + expect(findDependenciesCountBadge().text()).toBe(dependencyLinks().length.toString()); + expect(findDependencyRows()).toHaveLength(dependencyLinks().length); + }); }); }); |