Commit 0e569d16 authored by Mark Florian's avatar Mark Florian

Merge branch...

Merge branch '216761-add-image-repository-level-delete-functionality-to-the-image-repository-detail-view' into 'master'

Add delete functionality to the Image Repository detail view

See merge request gitlab-org/gitlab!51980
parents a7ad7fca c0ec6ddc
<script>
import { GlModal, GlSprintf } from '@gitlab/ui';
import { n__ } from '~/locale';
import { REMOVE_TAG_CONFIRMATION_TEXT, REMOVE_TAGS_CONFIRMATION_TEXT } from '../../constants/index';
import {
REMOVE_TAG_CONFIRMATION_TEXT,
REMOVE_TAGS_CONFIRMATION_TEXT,
DELETE_IMAGE_CONFIRMATION_TITLE,
DELETE_IMAGE_CONFIRMATION_TEXT,
} from '../../constants';
export default {
components: {
......@@ -14,9 +19,17 @@ export default {
required: false,
default: () => [],
},
deleteImage: {
type: Boolean,
default: false,
required: false,
},
},
computed: {
modalAction() {
modalTitle() {
if (this.deleteImage) {
return DELETE_IMAGE_CONFIRMATION_TITLE;
}
return n__(
'ContainerRegistry|Remove tag',
'ContainerRegistry|Remove tags',
......@@ -24,14 +37,19 @@ export default {
);
},
modalDescription() {
if (this.deleteImage) {
return {
message: DELETE_IMAGE_CONFIRMATION_TEXT,
};
}
if (this.itemsToBeDeleted.length > 1) {
return {
message: REMOVE_TAGS_CONFIRMATION_TEXT,
item: this.itemsToBeDeleted.length,
};
}
const [first] = this.itemsToBeDeleted;
const [first] = this.itemsToBeDeleted;
return {
message: REMOVE_TAG_CONFIRMATION_TEXT,
item: first?.path,
......@@ -51,16 +69,17 @@ export default {
ref="deleteModal"
modal-id="delete-tag-modal"
ok-variant="danger"
@ok="$emit('confirmDelete')"
:action-primary="{ text: __('Confirm'), attributes: { variant: 'danger' } }"
:action-cancel="{ text: __('Cancel') }"
@primary="$emit('confirmDelete')"
@cancel="$emit('cancelDelete')"
>
<template #modal-title>{{ modalAction }}</template>
<template #modal-ok>{{ modalAction }}</template>
<template #modal-title>{{ modalTitle }}</template>
<p v-if="modalDescription" data-testid="description">
<gl-sprintf :message="modalDescription.message">
<template #item
><b>{{ modalDescription.item }}</b></template
>
<template #item>
<b>{{ modalDescription.item }}</b>
</template>
</gl-sprintf>
</p>
</gl-modal>
......
<script>
import { GlSprintf } from '@gitlab/ui';
import { GlSprintf, GlButton } from '@gitlab/ui';
import { sprintf, n__ } from '~/locale';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
......@@ -24,7 +24,7 @@ import {
export default {
name: 'DetailsHeader',
components: { GlSprintf, TitleArea, MetadataItem },
components: { GlSprintf, GlButton, TitleArea, MetadataItem },
mixins: [timeagoMixin],
props: {
image: {
......@@ -36,6 +36,11 @@ export default {
required: false,
default: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
},
computed: {
visibilityIcon() {
......@@ -65,6 +70,9 @@ export default {
[UNFINISHED_STATUS]: { text: CLEANUP_UNFINISHED_TEXT, tooltip: CLEANUP_UNFINISHED_TOOLTIP },
}[this.image?.expirationPolicyCleanupStatus];
},
deleteButtonDisabled() {
return this.disabled || !this.image.canDelete;
},
},
i18n: {
DETAILS_PAGE_TITLE,
......@@ -75,11 +83,13 @@ export default {
<template>
<title-area :metadata-loading="metadataLoading">
<template #title>
<gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE">
<template #imageName>
{{ image.name }}
</template>
</gl-sprintf>
<span data-testid="title">
<gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE">
<template #imageName>
{{ image.name }}
</template>
</gl-sprintf>
</span>
</template>
<template #metadata-tags-count>
<metadata-item icon="tag" :text="tagCountText" data-testid="tags-count" />
......@@ -103,5 +113,15 @@ export default {
data-testid="updated-and-visibility"
/>
</template>
<template #right-actions>
<gl-button
v-if="!metadataLoading"
variant="danger"
:disabled="deleteButtonDisabled"
@click="$emit('delete')"
>
{{ __('Delete') }}
</gl-button>
</template>
</title-area>
</template>
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import {
IMAGE_STATUS_MESSAGES,
IMAGE_STATUS_TITLES,
IMAGE_STATUS_ALERT_TYPE,
PACKAGE_DELETE_HELP_PAGE_PATH,
} from '../../constants';
export default {
components: {
GlAlert,
GlSprintf,
GlLink,
},
props: {
status: {
type: String,
required: true,
},
},
computed: {
message() {
return IMAGE_STATUS_MESSAGES[this.status];
},
title() {
return IMAGE_STATUS_TITLES[this.status];
},
variant() {
return IMAGE_STATUS_ALERT_TYPE[this.status];
},
},
links: {
PACKAGE_DELETE_HELP_PAGE_PATH,
},
};
</script>
<template>
<gl-alert :title="title" :variant="variant">
<span data-testid="message">
<gl-sprintf :message="message">
<template #link="{ content }">
<gl-link :href="$options.links.PACKAGE_DELETE_HELP_PAGE_PATH" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</span>
</gl-alert>
</template>
......@@ -20,6 +20,11 @@ export default {
default: true,
required: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
},
i18n: {
REMOVE_TAGS_BUTTON_TITLE,
......@@ -37,6 +42,9 @@ export default {
showMultiDeleteButton() {
return this.tags.some((tag) => tag.canDelete) && !this.isMobile;
},
multiDeleteButtonIsDisabled() {
return !this.hasSelectedItems || this.disabled;
},
},
methods: {
updateSelectedItems(name) {
......@@ -55,7 +63,7 @@ export default {
<gl-button
v-if="showMultiDeleteButton"
:disabled="!hasSelectedItems"
:disabled="multiDeleteButtonIsDisabled"
category="secondary"
variant="danger"
@click="$emit('delete', selectedItems)"
......@@ -70,6 +78,7 @@ export default {
:first="index === 0"
:selected="selectedItems[tag.name]"
:is-mobile="isMobile"
:disabled="disabled"
@select="updateSelectedItems(tag.name)"
@delete="$emit('delete', { [tag.name]: true })"
/>
......
......@@ -12,6 +12,8 @@ import DetailsHeader from '../components/details_page/details_header.vue';
import TagsList from '../components/details_page/tags_list.vue';
import TagsLoader from '../components/details_page/tags_loader.vue';
import EmptyState from '../components/details_page/empty_state.vue';
import StatusAlert from '../components/details_page/status_alert.vue';
import DeleteImage from '../components/delete_image.vue';
import getContainerRepositoryDetailsQuery from '../graphql/queries/get_container_repository_details.query.graphql';
import deleteContainerRepositoryTagsMutation from '../graphql/mutations/delete_container_repository_tags.mutation.graphql';
......@@ -21,6 +23,7 @@ import {
ALERT_DANGER_TAG,
ALERT_SUCCESS_TAGS,
ALERT_DANGER_TAGS,
ALERT_DANGER_IMAGE,
GRAPHQL_PAGE_SIZE,
FETCH_IMAGES_LIST_ERROR_MESSAGE,
UNFINISHED_STATUS,
......@@ -38,6 +41,8 @@ export default {
TagsList,
TagsLoader,
EmptyState,
StatusAlert,
DeleteImage,
},
directives: {
GlResizeObserver: GlResizeObserverDirective,
......@@ -71,6 +76,7 @@ export default {
mutationLoading: false,
deleteAlertType: null,
hidePartialCleanupWarning: false,
deleteImageAlert: false,
};
},
computed: {
......@@ -105,6 +111,9 @@ export default {
hasNoTags() {
return this.tags.length === 0;
},
pageActionsAreDisabled() {
return Boolean(this.image?.status);
},
},
methods: {
updateBreadcrumb() {
......@@ -112,11 +121,19 @@ export default {
this.breadCrumbState.updateName(name);
},
deleteTags(toBeDeleted) {
this.deleteImageAlert = false;
this.itemsToBeDeleted = this.tags.filter((tag) => toBeDeleted[tag.name]);
this.track('click_button');
this.$refs.deleteModal.show();
},
async handleDelete() {
confirmDelete() {
if (this.deleteImageAlert) {
this.$refs.deleteImage.doDelete();
} else {
this.handleDeleteTag();
}
},
async handleDeleteTag() {
this.track('confirm_delete');
const { itemsToBeDeleted } = this;
this.itemsToBeDeleted = [];
......@@ -184,6 +201,18 @@ export default {
feature_name: this.config.userCalloutId,
});
},
deleteImage() {
this.deleteImageAlert = true;
this.itemsToBeDeleted = [{ path: this.image.path }];
this.$refs.deleteModal.show();
},
deleteImageError() {
this.deleteAlertType = ALERT_DANGER_IMAGE;
},
deleteImageIniit() {
this.itemsToBeDeleted = [];
this.mutationLoading = true;
},
},
};
</script>
......@@ -205,13 +234,25 @@ export default {
@dismiss="dismissPartialCleanupWarning"
/>
<details-header :image="image" :metadata-loading="isLoading" />
<status-alert v-if="image.status" :status="image.status" />
<details-header
:image="image"
:metadata-loading="isLoading"
:disabled="pageActionsAreDisabled"
@delete="deleteImage"
/>
<tags-loader v-if="isLoading" />
<template v-else>
<empty-state v-if="hasNoTags" :no-containers-image="config.noContainersImage" />
<template v-else>
<tags-list :tags="tags" :is-mobile="isMobile" @delete="deleteTags" />
<tags-list
:tags="tags"
:is-mobile="isMobile"
:disabled="pageActionsAreDisabled"
@delete="deleteTags"
/>
<div class="gl-display-flex gl-justify-content-center">
<gl-keyset-pagination
v-if="showPagination"
......@@ -225,10 +266,20 @@ export default {
</template>
</template>
<delete-image
:id="image.id"
ref="deleteImage"
use-update-fn
@start="deleteImageIniit"
@error="deleteImageError"
@end="mutationLoading = false"
/>
<delete-modal
ref="deleteModal"
:items-to-be-deleted="itemsToBeDeleted"
@confirmDelete="handleDelete"
:delete-image="deleteImageAlert"
@confirmDelete="confirmDelete"
@cancel="track('cancel_delete')"
/>
</template>
......
---
title: Add delete functionality to the Image Repository detail view
merge_request: 51980
author:
type: added
......@@ -4,6 +4,8 @@ import component from '~/registry/explorer/components/details_page/delete_modal.
import {
REMOVE_TAG_CONFIRMATION_TEXT,
REMOVE_TAGS_CONFIRMATION_TEXT,
DELETE_IMAGE_CONFIRMATION_TITLE,
DELETE_IMAGE_CONFIRMATION_TEXT,
} from '~/registry/explorer/constants';
import { GlModal } from '../../stubs';
......@@ -35,13 +37,13 @@ describe('Delete Modal', () => {
describe('events', () => {
it.each`
glEvent | localEvent
${'ok'} | ${'confirmDelete'}
${'cancel'} | ${'cancelDelete'}
glEvent | localEvent
${'primary'} | ${'confirmDelete'}
${'cancel'} | ${'cancelDelete'}
`('GlModal $glEvent emits $localEvent', ({ glEvent, localEvent }) => {
mountComponent();
findModal().vm.$emit(glEvent);
expect(wrapper.emitted(localEvent)).toBeTruthy();
expect(wrapper.emitted(localEvent)).toEqual([[]]);
});
});
......@@ -53,27 +55,51 @@ describe('Delete Modal', () => {
});
});
describe('itemsToBeDeleted contains one element', () => {
beforeEach(() => {
mountComponent({ itemsToBeDeleted: [{ path: 'foo' }] });
});
it(`has the correct description`, () => {
expect(findDescription().text()).toBe(REMOVE_TAG_CONFIRMATION_TEXT.replace('%{item}', 'foo'));
describe('when we are deleting images', () => {
it('has the correct title', () => {
mountComponent({ deleteImage: true });
expect(wrapper.text()).toContain(DELETE_IMAGE_CONFIRMATION_TITLE);
});
it('has the correct action', () => {
expect(wrapper.text()).toContain('Remove tag');
it('has the correct description', () => {
mountComponent({ deleteImage: true });
expect(wrapper.text()).toContain(DELETE_IMAGE_CONFIRMATION_TEXT);
});
});
describe('itemsToBeDeleted contains more than element', () => {
beforeEach(() => {
mountComponent({ itemsToBeDeleted: [{ path: 'foo' }, { path: 'bar' }] });
});
it(`has the correct description`, () => {
expect(findDescription().text()).toBe(REMOVE_TAGS_CONFIRMATION_TEXT.replace('%{item}', '2'));
describe('when we are deleting tags', () => {
describe('itemsToBeDeleted contains one element', () => {
beforeEach(() => {
mountComponent({ itemsToBeDeleted: [{ path: 'foo' }] });
});
it(`has the correct description`, () => {
expect(findDescription().text()).toBe(
REMOVE_TAG_CONFIRMATION_TEXT.replace('%{item}', 'foo'),
);
});
it('has the correct title', () => {
expect(wrapper.text()).toContain('Remove tag');
});
});
it('has the correct action', () => {
expect(wrapper.text()).toContain('Remove tags');
describe('itemsToBeDeleted contains more than element', () => {
beforeEach(() => {
mountComponent({ itemsToBeDeleted: [{ path: 'foo' }, { path: 'bar' }] });
});
it(`has the correct description`, () => {
expect(findDescription().text()).toBe(
REMOVE_TAGS_CONFIRMATION_TEXT.replace('%{item}', '2'),
);
});
it('has the correct title', () => {
expect(wrapper.text()).toContain('Remove tags');
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import { GlSprintf, GlButton } from '@gitlab/ui';
import { useFakeDate } from 'helpers/fake_date';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import component from '~/registry/explorer/components/details_page/details_header.vue';
......@@ -23,6 +23,7 @@ describe('Details Header', () => {
name: 'foo',
updatedAt: '2020-11-03T13:29:21Z',
tagsCount: 10,
canDelete: true,
project: {
visibility: 'public',
containerExpirationPolicy: {
......@@ -36,8 +37,10 @@ describe('Details Header', () => {
const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
const findLastUpdatedAndVisibility = () => findByTestId('updated-and-visibility');
const findTitle = () => findByTestId('title');
const findTagsCount = () => findByTestId('tags-count');
const findCleanup = () => findByTestId('cleanup');
const findDeleteButton = () => wrapper.find(GlButton);
const waitForMetadataItems = async () => {
// Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available
......@@ -45,11 +48,9 @@ describe('Details Header', () => {
await wrapper.vm.$nextTick();
};
const mountComponent = (image = defaultImage) => {
const mountComponent = (propsData = { image: defaultImage }) => {
wrapper = shallowMount(component, {
propsData: {
image,
},
propsData,
stubs: {
GlSprintf,
TitleArea,
......@@ -63,13 +64,65 @@ describe('Details Header', () => {
});
it('has the correct title ', () => {
mountComponent({ ...defaultImage, name: '' });
expect(wrapper.text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE);
mountComponent({ image: { ...defaultImage, name: '' } });
expect(findTitle().text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE);
});
it('shows imageName in the title', () => {
mountComponent();
expect(wrapper.text()).toContain('foo');
expect(findTitle().text()).toContain('foo');
});
describe('delete button', () => {
it('exists', () => {
mountComponent();
expect(findDeleteButton().exists()).toBe(true);
});
it('is hidden while loading', () => {
mountComponent({ image: defaultImage, metadataLoading: true });
expect(findDeleteButton().exists()).toBe(false);
});
it('has the correct text', () => {
mountComponent();
expect(findDeleteButton().text()).toBe('Delete');
});
it('has the correct props', () => {
mountComponent();
expect(findDeleteButton().props()).toMatchObject({
variant: 'danger',
disabled: false,
});
});
it('emits the correct event', () => {
mountComponent();
findDeleteButton().vm.$emit('click');
expect(wrapper.emitted('delete')).toEqual([[]]);
});
it.each`
canDelete | disabled | isDisabled
${true} | ${false} | ${false}
${true} | ${true} | ${true}
${false} | ${false} | ${true}
${false} | ${true} | ${true}
`(
'when canDelete is $canDelete and disabled is $disabled is $isDisabled that the button is disabled',
({ canDelete, disabled, isDisabled }) => {
mountComponent({ image: { ...defaultImage, canDelete }, disabled });
expect(findDeleteButton().props('disabled')).toBe(isDisabled);
},
);
});
describe('metadata items', () => {
......@@ -82,7 +135,7 @@ describe('Details Header', () => {
});
it('when there is one tag has the correct text', async () => {
mountComponent({ ...defaultImage, tagsCount: 1 });
mountComponent({ image: { ...defaultImage, tagsCount: 1 } });
await waitForMetadataItems();
expect(findTagsCount().props('text')).toBe('1 tag');
......@@ -124,10 +177,12 @@ describe('Details Header', () => {
'when the status is $status the text is $text and the tooltip is $tooltip',
async ({ status, text, tooltip }) => {
mountComponent({
...defaultImage,
expirationPolicyCleanupStatus: status,
project: {
containerExpirationPolicy: { enabled: true, nextRunAt: '2021-01-03T14:29:21Z' },
image: {
...defaultImage,
expirationPolicyCleanupStatus: status,
project: {
containerExpirationPolicy: { enabled: true, nextRunAt: '2021-01-03T14:29:21Z' },
},
},
});
await waitForMetadataItems();
......@@ -156,7 +211,7 @@ describe('Details Header', () => {
expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye');
});
it('shows an eye slashed when the project is not public', async () => {
mountComponent({ ...defaultImage, project: { visibility: 'private' } });
mountComponent({ image: { ...defaultImage, project: { visibility: 'private' } } });
await waitForMetadataItems();
expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye-slash');
......
import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
import component from '~/registry/explorer/components/details_page/status_alert.vue';
import {
DELETE_SCHEDULED,
DELETE_FAILED,
PACKAGE_DELETE_HELP_PAGE_PATH,
SCHEDULED_FOR_DELETION_STATUS_TITLE,
SCHEDULED_FOR_DELETION_STATUS_MESSAGE,
FAILED_DELETION_STATUS_TITLE,
FAILED_DELETION_STATUS_MESSAGE,
} from '~/registry/explorer/constants';
describe('Status Alert', () => {
let wrapper;
const findLink = () => wrapper.find(GlLink);
const findAlert = () => wrapper.find(GlAlert);
const findMessage = () => wrapper.find('[data-testid="message"]');
const mountComponent = (propsData) => {
wrapper = shallowMount(component, {
propsData,
stubs: {
GlSprintf,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it.each`
status | title | variant | message | link
${DELETE_SCHEDULED} | ${SCHEDULED_FOR_DELETION_STATUS_TITLE} | ${'info'} | ${SCHEDULED_FOR_DELETION_STATUS_MESSAGE} | ${PACKAGE_DELETE_HELP_PAGE_PATH}
${DELETE_FAILED} | ${FAILED_DELETION_STATUS_TITLE} | ${'warning'} | ${FAILED_DELETION_STATUS_MESSAGE} | ${''}
`(
`when the status is $status, title is $title, variant is $variant, message is $message and the link is $link`,
({ status, title, variant, message, link }) => {
mountComponent({ status });
expect(findMessage().text()).toMatchInterpolatedText(message);
expect(findAlert().props()).toMatchObject({
title,
variant,
});
if (link) {
expect(findLink().attributes()).toMatchObject({
target: '_blank',
href: link,
});
}
},
);
});
......@@ -70,18 +70,25 @@ describe('Tags List', () => {
});
});
it('is disabled when no item is selected', () => {
mountComponent();
it.each`
disabled | doSelect | buttonDisabled
${true} | ${false} | ${'true'}
${true} | ${true} | ${'true'}
${false} | ${false} | ${'true'}
${false} | ${true} | ${undefined}
`(
'is $buttonDisabled that the button is disabled when the component disabled state is $disabled and is $doSelect that the user selected a tag',
async ({ disabled, buttonDisabled, doSelect }) => {
mountComponent({ tags, disabled, isMobile: false });
expect(findDeleteButton().attributes('disabled')).toBe('true');
});
if (doSelect) {
findTagsListRow().at(0).vm.$emit('select');
await wrapper.vm.$nextTick();
}
it('is enabled when at least one item is selected', async () => {
mountComponent();
findTagsListRow().at(0).vm.$emit('select');
await wrapper.vm.$nextTick();
expect(findDeleteButton().attributes('disabled')).toBe(undefined);
});
expect(findDeleteButton().attributes('disabled')).toBe(buttonDisabled);
},
);
it('click event emits a deleted event with selected items', () => {
mountComponent();
......@@ -100,12 +107,13 @@ describe('Tags List', () => {
});
it('the correct props are bound to it', () => {
mountComponent();
mountComponent({ tags, disabled: true });
const rows = findTagsListRow();
expect(rows.at(0).attributes()).toMatchObject({
first: 'true',
disabled: 'true',
});
});
......
......@@ -12,11 +12,17 @@ import DetailsHeader from '~/registry/explorer/components/details_page/details_h
import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue';
import TagsList from '~/registry/explorer/components/details_page/tags_list.vue';
import EmptyTagsState from '~/registry/explorer/components/details_page/empty_state.vue';
import StatusAlert from '~/registry/explorer/components/details_page/status_alert.vue';
import DeleteImage from '~/registry/explorer/components/delete_image.vue';
import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.query.graphql';
import deleteContainerRepositoryTagsMutation from '~/registry/explorer/graphql/mutations/delete_container_repository_tags.mutation.graphql';
import { UNFINISHED_STATUS } from '~/registry/explorer/constants/index';
import {
UNFINISHED_STATUS,
DELETE_SCHEDULED,
ALERT_DANGER_IMAGE,
} from '~/registry/explorer/constants';
import {
graphQLImageDetailsMock,
......@@ -43,6 +49,8 @@ describe('Details Page', () => {
const findDetailsHeader = () => wrapper.find(DetailsHeader);
const findEmptyState = () => wrapper.find(EmptyTagsState);
const findPartialCleanupAlert = () => wrapper.find(PartialCleanupAlert);
const findStatusAlert = () => wrapper.find(StatusAlert);
const findDeleteImage = () => wrapper.find(DeleteImage);
const routeId = 1;
......@@ -88,6 +96,7 @@ describe('Details Page', () => {
apolloProvider,
stubs: {
DeleteModal,
DeleteImage,
},
mocks: {
$route: {
......@@ -507,4 +516,83 @@ describe('Details Page', () => {
expect(breadCrumbState.updateName).toHaveBeenCalledWith(containerRepositoryMock.name);
});
});
describe('when the image has a status different from null', () => {
const resolver = jest
.fn()
.mockResolvedValue(graphQLImageDetailsMock({ status: DELETE_SCHEDULED }));
it('disables all the actions', async () => {
mountComponent({ resolver });
await waitForApolloRequestRender();
expect(findDetailsHeader().props('disabled')).toBe(true);
expect(findTagsList().props('disabled')).toBe(true);
});
it('shows a status alert', async () => {
mountComponent({ resolver });
await waitForApolloRequestRender();
expect(findStatusAlert().exists()).toBe(true);
expect(findStatusAlert().props()).toMatchObject({
status: DELETE_SCHEDULED,
});
});
});
describe('delete the image', () => {
const mountComponentAndDeleteImage = async () => {
mountComponent();
await waitForApolloRequestRender();
findDetailsHeader().vm.$emit('delete');
await wrapper.vm.$nextTick();
};
it('on delete event it deletes the image', async () => {
await mountComponentAndDeleteImage();
findDeleteModal().vm.$emit('confirmDelete');
expect(findDeleteImage().emitted('start')).toEqual([[]]);
});
it('binds the correct props to the modal', async () => {
await mountComponentAndDeleteImage();
expect(findDeleteModal().props()).toMatchObject({
itemsToBeDeleted: [{ path: 'gitlab-org/gitlab-test/rails-12009' }],
deleteImage: true,
});
});
it('binds correctly to delete-image start and end events', async () => {
mountComponent();
findDeleteImage().vm.$emit('start');
await wrapper.vm.$nextTick();
expect(findTagsLoader().exists()).toBe(true);
findDeleteImage().vm.$emit('end');
await wrapper.vm.$nextTick();
expect(findTagsLoader().exists()).toBe(false);
});
it('binds correctly to delete-image error event', async () => {
mountComponent();
findDeleteImage().vm.$emit('error');
await wrapper.vm.$nextTick();
expect(findDeleteAlert().props('deleteAlertType')).toBe(ALERT_DANGER_IMAGE);
});
});
});
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