Commit c3ace554 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '330846-convert-package-list-page-to-use-apollo-graphql' into 'master'

Add delete package to refactored list

See merge request gitlab-org/gitlab!73159
parents 71d50990 ad82e502
...@@ -23,6 +23,7 @@ import PackageFiles from '~/packages_and_registries/package_registry/components/ ...@@ -23,6 +23,7 @@ import PackageFiles from '~/packages_and_registries/package_registry/components/
import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.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 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 VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue';
import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
import { import {
PACKAGE_TYPE_NUGET, PACKAGE_TYPE_NUGET,
PACKAGE_TYPE_COMPOSER, PACKAGE_TYPE_COMPOSER,
...@@ -35,12 +36,10 @@ import { ...@@ -35,12 +36,10 @@ import {
CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION, CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION,
SHOW_DELETE_SUCCESS_ALERT, SHOW_DELETE_SUCCESS_ALERT,
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
DELETE_PACKAGE_ERROR_MESSAGE,
DELETE_PACKAGE_FILE_ERROR_MESSAGE, DELETE_PACKAGE_FILE_ERROR_MESSAGE,
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
} from '~/packages_and_registries/package_registry/constants'; } 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 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 getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
...@@ -62,6 +61,7 @@ export default { ...@@ -62,6 +61,7 @@ export default {
AdditionalMetadata, AdditionalMetadata,
InstallationCommands, InstallationCommands,
PackageFiles, PackageFiles,
DeletePackage,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -148,40 +148,15 @@ export default { ...@@ -148,40 +148,15 @@ export default {
formatSize(size) { formatSize(size) {
return numberToHumanSize(size); return numberToHumanSize(size);
}, },
async deletePackage() { navigateToListWithSuccessModal() {
const { data } = await this.$apollo.mutate({ const returnTo =
mutation: destroyPackageMutation, !this.groupListUrl || document.referrer.includes(this.projectName)
variables: { ? this.projectListUrl
id: this.packageEntity.id, : this.groupListUrl; // to avoid security issue url are supplied from backend
},
});
if (data?.destroyPackage?.errors[0]) { const modalQuery = objectToQuery({ [SHOW_DELETE_SUCCESS_ALERT]: true });
throw data.destroyPackage.errors[0];
}
},
async confirmPackageDeletion() {
this.track(DELETE_PACKAGE_TRACKING_ACTION);
try {
await this.deletePackage();
const returnTo =
!this.groupListUrl || document.referrer.includes(this.projectName)
? this.projectListUrl
: this.groupListUrl; // to avoid security issue url are supplied from backend
const modalQuery = objectToQuery({ [SHOW_DELETE_SUCCESS_ALERT]: true });
window.location.replace(`${returnTo}?${modalQuery}`); window.location.replace(`${returnTo}?${modalQuery}`);
} catch (error) {
createFlash({
message: DELETE_PACKAGE_ERROR_MESSAGE,
type: 'warning',
captureError: true,
error,
});
}
}, },
async deletePackageFile(id) { async deletePackageFile(id) {
try { try {
...@@ -322,26 +297,33 @@ export default { ...@@ -322,26 +297,33 @@ export default {
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
<gl-modal <delete-package
ref="deleteModal" @start="track($options.trackingActions.DELETE_PACKAGE_TRACKING_ACTION)"
modal-id="delete-modal" @end="navigateToListWithSuccessModal"
data-testid="delete-modal"
:action-primary="$options.modal.packageDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
@primary="confirmPackageDeletion"
@canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE)"
> >
<template #modal-title>{{ $options.i18n.deleteModalTitle }}</template> <template #default="{ deletePackage }">
<gl-sprintf :message="$options.i18n.deleteModalContent"> <gl-modal
<template #version> ref="deleteModal"
<strong>{{ packageEntity.version }}</strong> modal-id="delete-modal"
</template> data-testid="delete-modal"
:action-primary="$options.modal.packageDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
@primary="deletePackage(packageEntity)"
@canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE)"
>
<template #modal-title>{{ $options.i18n.deleteModalTitle }}</template>
<gl-sprintf :message="$options.i18n.deleteModalContent">
<template #version>
<strong>{{ packageEntity.version }}</strong>
</template>
<template #name> <template #name>
<strong>{{ packageEntity.name }}</strong> <strong>{{ packageEntity.name }}</strong>
</template> </template>
</gl-sprintf> </gl-sprintf>
</gl-modal> </gl-modal>
</template>
</delete-package>
<gl-modal <gl-modal
ref="deleteFileModal" ref="deleteFileModal"
......
<script>
import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
import createFlash from '~/flash';
import { s__ } from '~/locale';
export default {
props: {
refetchQueries: {
type: Array,
required: false,
default: null,
},
showSuccessAlert: {
type: Boolean,
required: false,
default: false,
},
},
i18n: {
errorMessage: s__('PackageRegistry|Something went wrong while deleting the package.'),
successMessage: s__('PackageRegistry|Package deleted successfully'),
},
methods: {
async deletePackage(packageEntity) {
try {
this.$emit('start');
const { data } = await this.$apollo.mutate({
mutation: destroyPackageMutation,
variables: {
id: packageEntity.id,
},
awaitRefetchQueries: Boolean(this.refetchQueries),
refetchQueries: this.refetchQueries,
});
if (data?.destroyPackage?.errors[0]) {
throw data.destroyPackage.errors[0];
}
if (this.showSuccessAlert) {
createFlash({
message: this.$options.i18n.successMessage,
type: 'success',
});
}
} catch (error) {
createFlash({
message: this.$options.i18n.errorMessage,
type: 'warning',
captureError: true,
error,
});
}
this.$emit('end');
},
},
render() {
return this.$scopedSlots.default({ deletePackage: this.deletePackage });
},
};
</script>
<script> <script>
/*
* The following component has several commented lines, this is because we are refactoring them piece by piece on several mrs
* For a complete overview of the plan please check: https://gitlab.com/gitlab-org/gitlab/-/issues/330846
* This work is behind feature flag: https://gitlab.com/gitlab-org/gitlab/-/issues/341136
*/
import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { historyReplaceState } from '~/lib/utils/common_utils'; import { historyReplaceState } from '~/lib/utils/common_utils';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages/list/constants'; import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages/list/constants';
import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants'; import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
import { import {
PROJECT_RESOURCE_TYPE, PROJECT_RESOURCE_TYPE,
GROUP_RESOURCE_TYPE, GROUP_RESOURCE_TYPE,
LIST_QUERY_DEBOUNCE_TIME, LIST_QUERY_DEBOUNCE_TIME,
GRAPHQL_PAGE_SIZE, GRAPHQL_PAGE_SIZE,
} from '~/packages_and_registries/package_registry/constants'; } from '~/packages_and_registries/package_registry/constants';
import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
import PackageTitle from './package_title.vue'; import PackageTitle from './package_title.vue';
import PackageSearch from './package_search.vue'; import PackageSearch from './package_search.vue';
import PackageList from './packages_list.vue'; import PackageList from './packages_list.vue';
...@@ -29,6 +26,7 @@ export default { ...@@ -29,6 +26,7 @@ export default {
PackageList, PackageList,
PackageTitle, PackageTitle,
PackageSearch, PackageSearch,
DeletePackage,
}, },
inject: [ inject: [
'packageHelpUrl', 'packageHelpUrl',
...@@ -42,6 +40,7 @@ export default { ...@@ -42,6 +40,7 @@ export default {
packages: {}, packages: {},
sort: '', sort: '',
filters: {}, filters: {},
mutationLoading: false,
}; };
}, },
apollo: { apollo: {
...@@ -88,6 +87,17 @@ export default { ...@@ -88,6 +87,17 @@ export default {
? this.$options.i18n.emptyPageTitle ? this.$options.i18n.emptyPageTitle
: this.$options.i18n.noResultsTitle; : this.$options.i18n.noResultsTitle;
}, },
isLoading() {
return this.$apollo.queries.packages.loading || this.mutationLoading;
},
refetchQueriesData() {
return [
{
query: getPackagesQuery,
variables: this.queryVariables,
},
];
},
}, },
mounted() { mounted() {
this.checkDeleteAlert(); this.checkDeleteAlert();
...@@ -153,25 +163,35 @@ export default { ...@@ -153,25 +163,35 @@ export default {
<package-title :help-url="packageHelpUrl" :count="packagesCount" /> <package-title :help-url="packageHelpUrl" :count="packagesCount" />
<package-search @update="handleSearchUpdate" /> <package-search @update="handleSearchUpdate" />
<package-list <delete-package
:list="packages.nodes" :refetch-queries="refetchQueriesData"
:is-loading="$apollo.queries.packages.loading" show-success-alert
:page-info="pageInfo" @start="mutationLoading = true"
@prev-page="fetchPreviousPage" @end="mutationLoading = false"
@next-page="fetchNextPage"
> >
<template #empty-state> <template #default="{ deletePackage }">
<gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration"> <package-list
<template #description> :list="packages.nodes"
<gl-sprintf v-if="hasFilters" :message="$options.i18n.widenFilters" /> :is-loading="isLoading"
<gl-sprintf v-else :message="$options.i18n.noResultsText"> :page-info="pageInfo"
<template #noPackagesLink="{ content }"> @prev-page="fetchPreviousPage"
<gl-link :href="emptyListHelpUrl" target="_blank">{{ content }}</gl-link> @next-page="fetchNextPage"
@package:delete="deletePackage"
>
<template #empty-state>
<gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration">
<template #description>
<gl-sprintf v-if="hasFilters" :message="$options.i18n.widenFilters" />
<gl-sprintf v-else :message="$options.i18n.noResultsText">
<template #noPackagesLink="{ content }">
<gl-link :href="emptyListHelpUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</template> </template>
</gl-sprintf> </gl-empty-state>
</template> </template>
</gl-empty-state> </package-list>
</template> </template>
</package-list> </delete-package>
</div> </div>
</template> </template>
...@@ -60,21 +60,28 @@ export default { ...@@ -60,21 +60,28 @@ export default {
showPagination() { showPagination() {
return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage; return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage;
}, },
showDeleteModal: {
get() {
return Boolean(this.itemToBeDeleted);
},
set(value) {
if (!value) {
this.itemToBeDeleted = null;
}
},
},
}, },
methods: { methods: {
setItemToBeDeleted(item) { setItemToBeDeleted(item) {
this.itemToBeDeleted = { ...item }; this.itemToBeDeleted = { ...item };
this.track(REQUEST_DELETE_PACKAGE_TRACKING_ACTION); this.track(REQUEST_DELETE_PACKAGE_TRACKING_ACTION);
this.$refs.packageListDeleteModal.show();
}, },
deleteItemConfirmation() { deleteItemConfirmation() {
this.$emit('package:delete', this.itemToBeDeleted); this.$emit('package:delete', this.itemToBeDeleted);
this.track(DELETE_PACKAGE_TRACKING_ACTION); this.track(DELETE_PACKAGE_TRACKING_ACTION);
this.itemToBeDeleted = null;
}, },
deleteItemCanceled() { deleteItemCanceled() {
this.track(CANCEL_DELETE_PACKAGE_TRACKING_ACTION); this.track(CANCEL_DELETE_PACKAGE_TRACKING_ACTION);
this.itemToBeDeleted = null;
}, },
}, },
i18n: { i18n: {
...@@ -115,7 +122,7 @@ export default { ...@@ -115,7 +122,7 @@ export default {
</div> </div>
<gl-modal <gl-modal
ref="packageListDeleteModal" v-model="showDeleteModal"
modal-id="confirm-delete-pacakge" modal-id="confirm-delete-pacakge"
ok-variant="danger" ok-variant="danger"
@ok="deleteItemConfirmation" @ok="deleteItemConfirmation"
......
...@@ -60,9 +60,6 @@ export const TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND = ...@@ -60,9 +60,6 @@ export const TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND =
'copy_composer_package_include_command'; 'copy_composer_package_include_command';
export const SHOW_DELETE_SUCCESS_ALERT = 'showSuccessDeleteAlert'; export const SHOW_DELETE_SUCCESS_ALERT = 'showSuccessDeleteAlert';
export const DELETE_PACKAGE_ERROR_MESSAGE = s__(
'PackageRegistry|Something went wrong while deleting the package.',
);
export const DELETE_PACKAGE_FILE_ERROR_MESSAGE = s__( export const DELETE_PACKAGE_FILE_ERROR_MESSAGE = s__(
'PackageRegistry|Something went wrong while deleting the package file.', 'PackageRegistry|Something went wrong while deleting the package file.',
); );
......
...@@ -24512,6 +24512,9 @@ msgstr "" ...@@ -24512,6 +24512,9 @@ msgstr ""
msgid "PackageRegistry|Package Registry" msgid "PackageRegistry|Package Registry"
msgstr "" msgstr ""
msgid "PackageRegistry|Package deleted successfully"
msgstr ""
msgid "PackageRegistry|Package file deleted successfully" msgid "PackageRegistry|Package file deleted successfully"
msgstr "" msgstr ""
......
...@@ -16,16 +16,15 @@ import PackageFiles from '~/packages_and_registries/package_registry/components/ ...@@ -16,16 +16,15 @@ import PackageFiles from '~/packages_and_registries/package_registry/components/
import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.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 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 VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue';
import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
import { import {
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
DELETE_PACKAGE_ERROR_MESSAGE,
PACKAGE_TYPE_COMPOSER, PACKAGE_TYPE_COMPOSER,
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
DELETE_PACKAGE_FILE_ERROR_MESSAGE, DELETE_PACKAGE_FILE_ERROR_MESSAGE,
PACKAGE_TYPE_NUGET, PACKAGE_TYPE_NUGET,
} from '~/packages_and_registries/package_registry/constants'; } 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 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 getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql';
import { import {
...@@ -34,8 +33,6 @@ import { ...@@ -34,8 +33,6 @@ import {
packageVersions, packageVersions,
dependencyLinks, dependencyLinks,
emptyPackageDetailsQuery, emptyPackageDetailsQuery,
packageDestroyMutation,
packageDestroyMutationError,
packageFiles, packageFiles,
packageDestroyFileMutation, packageDestroyFileMutation,
packageDestroyFileMutationError, packageDestroyFileMutationError,
...@@ -64,14 +61,12 @@ describe('PackagesApp', () => { ...@@ -64,14 +61,12 @@ describe('PackagesApp', () => {
function createComponent({ function createComponent({
resolver = jest.fn().mockResolvedValue(packageDetailsQuery()), resolver = jest.fn().mockResolvedValue(packageDetailsQuery()),
mutationResolver = jest.fn().mockResolvedValue(packageDestroyMutation()),
fileDeleteMutationResolver = jest.fn().mockResolvedValue(packageDestroyFileMutation()), fileDeleteMutationResolver = jest.fn().mockResolvedValue(packageDestroyFileMutation()),
} = {}) { } = {}) {
localVue.use(VueApollo); localVue.use(VueApollo);
const requestHandlers = [ const requestHandlers = [
[getPackageDetails, resolver], [getPackageDetails, resolver],
[destroyPackageMutation, mutationResolver],
[destroyPackageFileMutation, fileDeleteMutationResolver], [destroyPackageFileMutation, fileDeleteMutationResolver],
]; ];
apolloProvider = createMockApollo(requestHandlers); apolloProvider = createMockApollo(requestHandlers);
...@@ -82,6 +77,7 @@ describe('PackagesApp', () => { ...@@ -82,6 +77,7 @@ describe('PackagesApp', () => {
provide, provide,
stubs: { stubs: {
PackageTitle, PackageTitle,
DeletePackage,
GlModal: { GlModal: {
template: '<div></div>', template: '<div></div>',
methods: { methods: {
...@@ -108,6 +104,7 @@ describe('PackagesApp', () => { ...@@ -108,6 +104,7 @@ describe('PackagesApp', () => {
const findDependenciesCountBadge = () => wrapper.findComponent(GlBadge); const findDependenciesCountBadge = () => wrapper.findComponent(GlBadge);
const findNoDependenciesMessage = () => wrapper.findByTestId('no-dependencies-message'); const findNoDependenciesMessage = () => wrapper.findByTestId('no-dependencies-message');
const findDependencyRows = () => wrapper.findAllComponents(DependencyRow); const findDependencyRows = () => wrapper.findAllComponents(DependencyRow);
const findDeletePackage = () => wrapper.findComponent(DeletePackage);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -187,14 +184,6 @@ describe('PackagesApp', () => { ...@@ -187,14 +184,6 @@ describe('PackagesApp', () => {
}); });
}; };
const performDeletePackage = async () => {
await findDeleteButton().trigger('click');
findDeleteModal().vm.$emit('primary');
await waitForPromises();
};
afterEach(() => { afterEach(() => {
Object.defineProperty(document, 'referrer', { Object.defineProperty(document, 'referrer', {
value: originalReferrer, value: originalReferrer,
...@@ -220,7 +209,7 @@ describe('PackagesApp', () => { ...@@ -220,7 +209,7 @@ describe('PackagesApp', () => {
await waitForPromises(); await waitForPromises();
await performDeletePackage(); findDeletePackage().vm.$emit('end');
expect(window.location.replace).toHaveBeenCalledWith( expect(window.location.replace).toHaveBeenCalledWith(
'projectListUrl?showSuccessDeleteAlert=true', 'projectListUrl?showSuccessDeleteAlert=true',
...@@ -234,45 +223,13 @@ describe('PackagesApp', () => { ...@@ -234,45 +223,13 @@ describe('PackagesApp', () => {
await waitForPromises(); await waitForPromises();
await performDeletePackage(); findDeletePackage().vm.$emit('end');
expect(window.location.replace).toHaveBeenCalledWith( expect(window.location.replace).toHaveBeenCalledWith(
'groupListUrl?showSuccessDeleteAlert=true', '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', () => { describe('package files', () => {
......
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import createFlash from '~/flash';
import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
import {
packageDestroyMutation,
packageDestroyMutationError,
packagesListQuery,
} from '../../mock_data';
jest.mock('~/flash');
const localVue = createLocalVue();
describe('DeletePackage', () => {
let wrapper;
let apolloProvider;
let resolver;
let mutationResolver;
const eventPayload = { id: '1' };
function createComponent(propsData = {}) {
localVue.use(VueApollo);
const requestHandlers = [
[getPackagesQuery, resolver],
[destroyPackageMutation, mutationResolver],
];
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(DeletePackage, {
propsData,
localVue,
apolloProvider,
scopedSlots: {
default(props) {
return this.$createElement('button', {
attrs: {
'data-testid': 'trigger-button',
},
on: {
click: props.deletePackage,
},
});
},
},
});
}
const findButton = () => wrapper.findByTestId('trigger-button');
const clickOnButtonAndWait = (payload) => {
findButton().trigger('click', payload);
return waitForPromises();
};
beforeEach(() => {
resolver = jest.fn().mockResolvedValue(packagesListQuery());
mutationResolver = jest.fn().mockResolvedValue(packageDestroyMutation());
});
afterEach(() => {
wrapper.destroy();
});
it('binds deletePackage method to the default slot', () => {
createComponent();
findButton().trigger('click');
expect(wrapper.emitted('start')).toEqual([[]]);
});
it('calls apollo mutation', async () => {
createComponent();
await clickOnButtonAndWait(eventPayload);
expect(mutationResolver).toHaveBeenCalledWith(eventPayload);
});
it('passes refetchQueries to apollo mutate', async () => {
const variables = { isGroupPage: true };
createComponent({
refetchQueries: [{ query: getPackagesQuery, variables }],
});
await clickOnButtonAndWait(eventPayload);
expect(mutationResolver).toHaveBeenCalledWith(eventPayload);
expect(resolver).toHaveBeenCalledWith(variables);
});
describe('on mutation success', () => {
it('emits end event', async () => {
createComponent();
await clickOnButtonAndWait(eventPayload);
expect(wrapper.emitted('end')).toEqual([[]]);
});
it('does not call createFlash', async () => {
createComponent();
await clickOnButtonAndWait(eventPayload);
expect(createFlash).not.toHaveBeenCalled();
});
it('calls createFlash with the success message when showSuccessAlert is true', async () => {
createComponent({ showSuccessAlert: true });
await clickOnButtonAndWait(eventPayload);
expect(createFlash).toHaveBeenCalledWith({
message: DeletePackage.i18n.successMessage,
type: 'success',
});
});
});
describe.each`
errorType | mutationResolverResponse
${'connectionError'} | ${jest.fn().mockRejectedValue()}
${'localError'} | ${jest.fn().mockResolvedValue(packageDestroyMutationError())}
`('on mutation $errorType', ({ mutationResolverResponse }) => {
beforeEach(() => {
mutationResolver = mutationResolverResponse;
});
it('emits end event', async () => {
createComponent();
await clickOnButtonAndWait(eventPayload);
expect(wrapper.emitted('end')).toEqual([[]]);
});
it('calls createFlash with the error message', async () => {
createComponent({ showSuccessAlert: true });
await clickOnButtonAndWait(eventPayload);
expect(createFlash).toHaveBeenCalledWith({
message: DeletePackage.i18n.errorMessage,
type: 'warning',
captureError: true,
error: expect.any(Error),
});
});
});
});
...@@ -10,6 +10,7 @@ import PackageListApp from '~/packages_and_registries/package_registry/component ...@@ -10,6 +10,7 @@ import PackageListApp from '~/packages_and_registries/package_registry/component
import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue';
import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue'; import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue';
import OriginalPackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue'; import OriginalPackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue';
import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
import { import {
PROJECT_RESOURCE_TYPE, PROJECT_RESOURCE_TYPE,
...@@ -55,6 +56,7 @@ describe('PackagesListApp', () => { ...@@ -55,6 +56,7 @@ describe('PackagesListApp', () => {
const findSearch = () => wrapper.findComponent(PackageSearch); const findSearch = () => wrapper.findComponent(PackageSearch);
const findListComponent = () => wrapper.findComponent(PackageList); const findListComponent = () => wrapper.findComponent(PackageList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findDeletePackage = () => wrapper.findComponent(DeletePackage);
const mountComponent = ({ const mountComponent = ({
resolver = jest.fn().mockResolvedValue(packagesListQuery()), resolver = jest.fn().mockResolvedValue(packagesListQuery()),
...@@ -72,9 +74,10 @@ describe('PackagesListApp', () => { ...@@ -72,9 +74,10 @@ describe('PackagesListApp', () => {
stubs: { stubs: {
GlEmptyState, GlEmptyState,
GlLoadingIcon, GlLoadingIcon,
PackageList,
GlSprintf, GlSprintf,
GlLink, GlLink,
PackageList,
DeletePackage,
}, },
}); });
}; };
...@@ -228,4 +231,45 @@ describe('PackagesListApp', () => { ...@@ -228,4 +231,45 @@ describe('PackagesListApp', () => {
expect(findEmptyState().text()).toContain(PackageListApp.i18n.widenFilters); expect(findEmptyState().text()).toContain(PackageListApp.i18n.widenFilters);
}); });
}); });
describe('delete package', () => {
it('exists and has the correct props', async () => {
mountComponent();
await waitForDebouncedApollo();
expect(findDeletePackage().props()).toMatchObject({
refetchQueries: [{ query: getPackagesQuery, variables: {} }],
showSuccessAlert: true,
});
});
it('deletePackage is bound to package-list package:delete event', async () => {
mountComponent();
await waitForDebouncedApollo();
findListComponent().vm.$emit('package:delete', { id: 1 });
expect(findDeletePackage().emitted('start')).toEqual([[]]);
});
it('start and end event set loading correctly', async () => {
mountComponent();
await waitForDebouncedApollo();
findDeletePackage().vm.$emit('start');
await nextTick();
expect(findListComponent().props('isLoading')).toBe(true);
findDeletePackage().vm.$emit('end');
await nextTick();
expect(findListComponent().props('isLoading')).toBe(false);
});
});
}); });
...@@ -122,14 +122,6 @@ describe('packages_list', () => { ...@@ -122,14 +122,6 @@ describe('packages_list', () => {
expect(findPackageListDeleteModal().text()).toContain(firstPackage.name); expect(findPackageListDeleteModal().text()).toContain(firstPackage.name);
}); });
it('confirming delete empties itemsToBeDeleted', async () => {
findPackageListDeleteModal().vm.$emit('ok');
await nextTick();
expect(findPackageListDeleteModal().text()).not.toContain(firstPackage.name);
});
it('confirming on the modal emits package:delete', async () => { it('confirming on the modal emits package:delete', async () => {
findPackageListDeleteModal().vm.$emit('ok'); findPackageListDeleteModal().vm.$emit('ok');
...@@ -138,8 +130,9 @@ describe('packages_list', () => { ...@@ -138,8 +130,9 @@ describe('packages_list', () => {
expect(wrapper.emitted('package:delete')[0]).toEqual([firstPackage]); expect(wrapper.emitted('package:delete')[0]).toEqual([firstPackage]);
}); });
it('cancel event resets itemToBeDeleted', async () => { it('closing the modal resets itemToBeDeleted', async () => {
findPackageListDeleteModal().vm.$emit('cancel'); // triggering the v-model
findPackageListDeleteModal().vm.$emit('input', false);
await nextTick(); await nextTick();
......
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