Commit 9d298d61 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Kushal Pandya

Refactor toast for alerts

- move i18n to constants
- remove toast
- add alert
- WIP unit test
parent f31df951
...@@ -39,14 +39,20 @@ export const DELETE_IMAGE_SUCCESS_MESSAGE = s__( ...@@ -39,14 +39,20 @@ export const DELETE_IMAGE_SUCCESS_MESSAGE = s__(
// Image details page // Image details page
export const DETAILS_PAGE_TITLE = s__('ContainerRegistry|%{imageName} tags');
export const DELETE_TAG_ERROR_MESSAGE = s__( export const DELETE_TAG_ERROR_MESSAGE = s__(
'ContainerRegistry|Something went wrong while deleting the tag.', 'ContainerRegistry|Something went wrong while marking the tag for deletion.',
);
export const DELETE_TAG_SUCCESS_MESSAGE = s__(
'ContainerRegistry|Tag successfully marked for deletion.',
); );
export const DELETE_TAG_SUCCESS_MESSAGE = s__('ContainerRegistry|Tag deleted successfully');
export const DELETE_TAGS_ERROR_MESSAGE = s__( export const DELETE_TAGS_ERROR_MESSAGE = s__(
'ContainerRegistry|Something went wrong while deleting the tags.', 'ContainerRegistry|Something went wrong while marking the tags for deletion.',
);
export const DELETE_TAGS_SUCCESS_MESSAGE = s__(
'ContainerRegistry|Tags successfully marked for deletion.',
); );
export const DELETE_TAGS_SUCCESS_MESSAGE = s__('ContainerRegistry|Tags deleted successfully');
export const DEFAULT_PAGE = 1; export const DEFAULT_PAGE = 1;
export const DEFAULT_PAGE_SIZE = 10; export const DEFAULT_PAGE_SIZE = 10;
...@@ -65,6 +71,27 @@ export const LIST_LABEL_IMAGE_ID = s__('ContainerRegistry|Image ID'); ...@@ -65,6 +71,27 @@ export const LIST_LABEL_IMAGE_ID = s__('ContainerRegistry|Image ID');
export const LIST_LABEL_SIZE = s__('ContainerRegistry|Compressed Size'); export const LIST_LABEL_SIZE = s__('ContainerRegistry|Compressed Size');
export const LIST_LABEL_LAST_UPDATED = s__('ContainerRegistry|Last Updated'); export const LIST_LABEL_LAST_UPDATED = s__('ContainerRegistry|Last Updated');
export const REMOVE_TAG_BUTTON_TITLE = s__('ContainerRegistry|Remove tag');
export const REMOVE_TAGS_BUTTON_TITLE = s__('ContainerRegistry|Remove selected tags');
export const REMOVE_TAG_CONFIRMATION_TEXT = s__(
`ContainerRegistry|You are about to remove %{item}. Are you sure?`,
);
export const REMOVE_TAGS_CONFIRMATION_TEXT = s__(
`ContainerRegistry|You are about to remove %{item} tags. Are you sure?`,
);
export const EMPTY_IMAGE_REPOSITORY_TITLE = s__('ContainerRegistry|This image has no active tags');
export const EMPTY_IMAGE_REPOSITORY_MESSAGE = s__(
`ContainerRegistry|The last tag related to this image was recently removed.
This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process.
If you have any questions, contact your administrator.`,
);
export const ADMIN_GARBAGE_COLLECTION_TIP = s__(
'ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage.',
);
// Expiration policies // Expiration policies
export const EXPIRATION_POLICY_ALERT_TITLE = s__( export const EXPIRATION_POLICY_ALERT_TITLE = s__(
......
...@@ -9,12 +9,14 @@ import { ...@@ -9,12 +9,14 @@ import {
GlPagination, GlPagination,
GlModal, GlModal,
GlSprintf, GlSprintf,
GlAlert,
GlLink,
GlEmptyState, GlEmptyState,
GlResizeObserverDirective, GlResizeObserverDirective,
GlSkeletonLoader, GlSkeletonLoader,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { n__, s__ } from '~/locale'; import { n__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
...@@ -35,6 +37,14 @@ import { ...@@ -35,6 +37,14 @@ import {
DELETE_TAG_ERROR_MESSAGE, DELETE_TAG_ERROR_MESSAGE,
DELETE_TAGS_SUCCESS_MESSAGE, DELETE_TAGS_SUCCESS_MESSAGE,
DELETE_TAGS_ERROR_MESSAGE, DELETE_TAGS_ERROR_MESSAGE,
REMOVE_TAG_CONFIRMATION_TEXT,
REMOVE_TAGS_CONFIRMATION_TEXT,
DETAILS_PAGE_TITLE,
REMOVE_TAGS_BUTTON_TITLE,
REMOVE_TAG_BUTTON_TITLE,
EMPTY_IMAGE_REPOSITORY_TITLE,
EMPTY_IMAGE_REPOSITORY_MESSAGE,
ADMIN_GARBAGE_COLLECTION_TIP,
} from '../constants'; } from '../constants';
export default { export default {
...@@ -49,6 +59,8 @@ export default { ...@@ -49,6 +59,8 @@ export default {
GlSkeletonLoader, GlSkeletonLoader,
GlSprintf, GlSprintf,
GlEmptyState, GlEmptyState,
GlAlert,
GlLink,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -60,6 +72,19 @@ export default { ...@@ -60,6 +72,19 @@ export default {
width: 1000, width: 1000,
height: 40, height: 40,
}, },
i18n: {
DETAILS_PAGE_TITLE,
REMOVE_TAGS_BUTTON_TITLE,
REMOVE_TAG_BUTTON_TITLE,
EMPTY_IMAGE_REPOSITORY_TITLE,
EMPTY_IMAGE_REPOSITORY_MESSAGE,
},
alertMessages: {
success_tag: DELETE_TAG_SUCCESS_MESSAGE,
danger_tag: DELETE_TAG_ERROR_MESSAGE,
success_tags: DELETE_TAGS_SUCCESS_MESSAGE,
danger_tags: DELETE_TAGS_ERROR_MESSAGE,
},
data() { data() {
return { return {
selectedItems: [], selectedItems: [],
...@@ -67,6 +92,7 @@ export default { ...@@ -67,6 +92,7 @@ export default {
selectAllChecked: false, selectAllChecked: false,
modalDescription: null, modalDescription: null,
isDesktop: true, isDesktop: true,
deleteAlertType: false,
}; };
}, },
computed: { computed: {
...@@ -110,20 +136,40 @@ export default { ...@@ -110,20 +136,40 @@ export default {
this.requestTagsList({ pagination: { page }, params: this.$route.params.id }); this.requestTagsList({ pagination: { page }, params: this.$route.params.id });
}, },
}, },
deleteAlertConfig() {
const config = {
title: '',
message: '',
type: 'success',
};
if (this.deleteAlertType) {
[config.type] = this.deleteAlertType.split('_');
const defaultMessage = this.$options.alertMessages[this.deleteAlertType];
if (this.config.isAdmin && config.type === 'success') {
config.title = defaultMessage;
config.message = ADMIN_GARBAGE_COLLECTION_TIP;
} else {
config.message = defaultMessage;
}
}
return config;
},
}, },
methods: { methods: {
...mapActions(['requestTagsList', 'requestDeleteTag', 'requestDeleteTags']), ...mapActions(['requestTagsList', 'requestDeleteTag', 'requestDeleteTags']),
setModalDescription(itemIndex = -1) { setModalDescription(itemIndex = -1) {
if (itemIndex === -1) { if (itemIndex === -1) {
this.modalDescription = { this.modalDescription = {
message: s__(`ContainerRegistry|You are about to remove %{item} tags. Are you sure?`), message: REMOVE_TAGS_CONFIRMATION_TEXT,
item: this.itemsToBeDeleted.length, item: this.itemsToBeDeleted.length,
}; };
} else { } else {
const { path } = this.tags[itemIndex]; const { path } = this.tags[itemIndex];
this.modalDescription = { this.modalDescription = {
message: s__(`ContainerRegistry|You are about to remove %{item}. Are you sure?`), message: REMOVE_TAG_CONFIRMATION_TEXT,
item: path, item: path,
}; };
} }
...@@ -179,19 +225,17 @@ export default { ...@@ -179,19 +225,17 @@ export default {
this.track('click_button'); this.track('click_button');
this.$refs.deleteModal.show(); this.$refs.deleteModal.show();
}, },
handleSingleDelete(itemToDelete) { handleSingleDelete(index) {
const itemToDelete = this.tags[index];
this.itemsToBeDeleted = []; this.itemsToBeDeleted = [];
this.selectedItems = this.selectedItems.filter(i => i !== index);
return this.requestDeleteTag({ tag: itemToDelete, params: this.$route.params.id }) return this.requestDeleteTag({ tag: itemToDelete, params: this.$route.params.id })
.then(() => .then(() => {
this.$toast.show(DELETE_TAG_SUCCESS_MESSAGE, { this.deleteAlertType = 'success_tag';
type: 'success', })
}), .catch(() => {
) this.deleteAlertType = 'danger_tag';
.catch(() => });
this.$toast.show(DELETE_TAG_ERROR_MESSAGE, {
type: 'error',
}),
);
}, },
handleMultipleDelete() { handleMultipleDelete() {
const { itemsToBeDeleted } = this; const { itemsToBeDeleted } = this;
...@@ -202,24 +246,19 @@ export default { ...@@ -202,24 +246,19 @@ export default {
ids: itemsToBeDeleted.map(x => this.tags[x].name), ids: itemsToBeDeleted.map(x => this.tags[x].name),
params: this.$route.params.id, params: this.$route.params.id,
}) })
.then(() => .then(() => {
this.$toast.show(DELETE_TAGS_SUCCESS_MESSAGE, { this.deleteAlertType = 'success_tags';
type: 'success', })
}), .catch(() => {
) this.deleteAlertType = 'danger_tags';
.catch(() => });
this.$toast.show(DELETE_TAGS_ERROR_MESSAGE, {
type: 'error',
}),
);
}, },
onDeletionConfirmed() { onDeletionConfirmed() {
this.track('confirm_delete'); this.track('confirm_delete');
if (this.isMultiDelete) { if (this.isMultiDelete) {
this.handleMultipleDelete(); this.handleMultipleDelete();
} else { } else {
const index = this.itemsToBeDeleted[0]; this.handleSingleDelete(this.itemsToBeDeleted[0]);
this.handleSingleDelete(this.tags[index]);
} }
}, },
handleResize() { handleResize() {
...@@ -231,9 +270,24 @@ export default { ...@@ -231,9 +270,24 @@ export default {
<template> <template>
<div v-gl-resize-observer="handleResize" class="my-3 w-100 slide-enter-to-element"> <div v-gl-resize-observer="handleResize" class="my-3 w-100 slide-enter-to-element">
<gl-alert
v-if="deleteAlertType"
:variant="deleteAlertConfig.type"
:title="deleteAlertConfig.title"
class="my-2"
@dismiss="deleteAlertType = null"
>
<gl-sprintf :message="deleteAlertConfig.message">
<template #docLink="{content}">
<gl-link :href="config.garbageCollectionHelpPagePath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-alert>
<div class="d-flex my-3 align-items-center"> <div class="d-flex my-3 align-items-center">
<h4> <h4>
<gl-sprintf :message="s__('ContainerRegistry|%{imageName} tags')"> <gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE">
<template #imageName> <template #imageName>
{{ imageName }} {{ imageName }}
</template> </template>
...@@ -256,8 +310,8 @@ export default { ...@@ -256,8 +310,8 @@ export default {
:disabled="!selectedItems || selectedItems.length === 0" :disabled="!selectedItems || selectedItems.length === 0"
class="float-right" class="float-right"
variant="danger" variant="danger"
:title="s__('ContainerRegistry|Remove selected tags')" :title="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
:aria-label="s__('ContainerRegistry|Remove selected tags')" :aria-label="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
@click="deleteMultipleItems()" @click="deleteMultipleItems()"
> >
<gl-icon name="remove" /> <gl-icon name="remove" />
...@@ -306,8 +360,8 @@ export default { ...@@ -306,8 +360,8 @@ export default {
<template #cell(actions)="{index, item}"> <template #cell(actions)="{index, item}">
<gl-deprecated-button <gl-deprecated-button
ref="singleDeleteButton" ref="singleDeleteButton"
:title="s__('ContainerRegistry|Remove tag')" :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
:aria-label="s__('ContainerRegistry|Remove tag')" :aria-label="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
:disabled="!item.destroy_path" :disabled="!item.destroy_path"
variant="danger" variant="danger"
class="js-delete-registry float-right btn-inverted btn-border-color btn-icon" class="js-delete-registry float-right btn-inverted btn-border-color btn-icon"
...@@ -337,15 +391,9 @@ export default { ...@@ -337,15 +391,9 @@ export default {
</template> </template>
<gl-empty-state <gl-empty-state
v-else v-else
:title="s__('ContainerRegistry|This image has no active tags')" :title="$options.i18n.EMPTY_IMAGE_REPOSITORY_TITLE"
:svg-path="config.noContainersImage" :svg-path="config.noContainersImage"
:description=" :description="$options.i18n.EMPTY_IMAGE_REPOSITORY_MESSAGE"
s__(
`ContainerRegistry|The last tag related to this image was recently removed.
This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process.
If you have any questions, contact your administrator.`,
)
"
class="mx-auto my-0" class="mx-auto my-0"
/> />
</template> </template>
......
<script> <script>
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; export default {};
import { mapState, mapActions, mapGetters } from 'vuex';
import { s__ } from '~/locale';
export default {
components: {
GlAlert,
GlSprintf,
GlLink,
},
i18n: {
garbageCollectionTipText: s__(
'ContainerRegistry|This Registry contains deleted image tag data. Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage.',
),
},
computed: {
...mapState(['config']),
...mapGetters(['showGarbageCollection']),
},
methods: {
...mapActions(['setShowGarbageCollectionTip']),
},
};
</script> </script>
<template> <template>
<div> <div>
<gl-alert
v-if="showGarbageCollection"
variant="tip"
class="my-2"
@dismiss="setShowGarbageCollectionTip(false)"
>
<gl-sprintf :message="$options.i18n.garbageCollectionTipText">
<template #docLink="{content}">
<gl-link :href="config.garbageCollectionHelpPagePath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-alert>
<transition name="slide"> <transition name="slide">
<router-view ref="router-view" /> <router-view ref="router-view" />
</transition> </transition>
......
...@@ -66,7 +66,7 @@ export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) = ...@@ -66,7 +66,7 @@ export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) =
dispatch('setShowGarbageCollectionTip', true); dispatch('setShowGarbageCollectionTip', true);
return dispatch('requestTagsList', { pagination: state.tagsPagination, params }); return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
}) })
.catch(() => { .finally(() => {
commit(types.SET_MAIN_LOADING, false); commit(types.SET_MAIN_LOADING, false);
}); });
}; };
...@@ -83,7 +83,7 @@ export const requestDeleteTags = ({ commit, dispatch, state }, { ids, params }) ...@@ -83,7 +83,7 @@ export const requestDeleteTags = ({ commit, dispatch, state }, { ids, params })
dispatch('setShowGarbageCollectionTip', true); dispatch('setShowGarbageCollectionTip', true);
return dispatch('requestTagsList', { pagination: state.tagsPagination, params }); return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
}) })
.catch(() => { .finally(() => {
commit(types.SET_MAIN_LOADING, false); commit(types.SET_MAIN_LOADING, false);
}); });
}; };
......
---
title: Use alerts instead of toasts in Image Repository details
merge_request: 29685
author:
type: changed
...@@ -5713,6 +5713,9 @@ msgstr "" ...@@ -5713,6 +5713,9 @@ msgstr ""
msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}" msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
msgstr "" msgstr ""
msgid "ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
msgstr ""
msgid "ContainerRegistry|Remove repository" msgid "ContainerRegistry|Remove repository"
msgstr "" msgstr ""
...@@ -5727,19 +5730,19 @@ msgstr[1] "" ...@@ -5727,19 +5730,19 @@ msgstr[1] ""
msgid "ContainerRegistry|Retention policy has been Enabled" msgid "ContainerRegistry|Retention policy has been Enabled"
msgstr "" msgstr ""
msgid "ContainerRegistry|Something went wrong while deleting the tag." msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
msgstr "" msgstr ""
msgid "ContainerRegistry|Something went wrong while deleting the tags." msgid "ContainerRegistry|Something went wrong while fetching the repository list."
msgstr "" msgstr ""
msgid "ContainerRegistry|Something went wrong while fetching the expiration policy." msgid "ContainerRegistry|Something went wrong while fetching the tags list."
msgstr "" msgstr ""
msgid "ContainerRegistry|Something went wrong while fetching the repository list." msgid "ContainerRegistry|Something went wrong while marking the tag for deletion."
msgstr "" msgstr ""
msgid "ContainerRegistry|Something went wrong while fetching the tags list." msgid "ContainerRegistry|Something went wrong while marking the tags for deletion."
msgstr "" msgstr ""
msgid "ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again." msgid "ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again."
...@@ -5751,16 +5754,16 @@ msgstr "" ...@@ -5751,16 +5754,16 @@ msgstr ""
msgid "ContainerRegistry|Tag" msgid "ContainerRegistry|Tag"
msgstr "" msgstr ""
msgid "ContainerRegistry|Tag deleted successfully"
msgstr ""
msgid "ContainerRegistry|Tag expiration policy" msgid "ContainerRegistry|Tag expiration policy"
msgstr "" msgstr ""
msgid "ContainerRegistry|Tag expiration policy is designed to:" msgid "ContainerRegistry|Tag expiration policy is designed to:"
msgstr "" msgstr ""
msgid "ContainerRegistry|Tags deleted successfully" msgid "ContainerRegistry|Tag successfully marked for deletion."
msgstr ""
msgid "ContainerRegistry|Tags successfully marked for deletion."
msgstr "" msgstr ""
msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}" msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}"
...@@ -5793,9 +5796,6 @@ msgstr "" ...@@ -5793,9 +5796,6 @@ msgstr ""
msgid "ContainerRegistry|There was an error during the deletion of this image repository, please try again." msgid "ContainerRegistry|There was an error during the deletion of this image repository, please try again."
msgstr "" msgstr ""
msgid "ContainerRegistry|This Registry contains deleted image tag data. Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
msgstr ""
msgid "ContainerRegistry|This image has no active tags" msgid "ContainerRegistry|This image has no active tags"
msgstr "" msgstr ""
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { GlTable, GlPagination, GlSkeletonLoader } from '@gitlab/ui'; import { GlTable, GlPagination, GlSkeletonLoader, GlAlert, GlLink } from '@gitlab/ui';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import stubChildren from 'helpers/stub_children'; import stubChildren from 'helpers/stub_children';
import component from '~/registry/explorer/pages/details.vue'; import component from '~/registry/explorer/pages/details.vue';
import store from '~/registry/explorer/stores/'; import { createStore } from '~/registry/explorer/stores/';
import { SET_MAIN_LOADING } from '~/registry/explorer/stores/mutation_types/'; import { SET_MAIN_LOADING, SET_INITIAL_STATE } from '~/registry/explorer/stores/mutation_types/';
import { import {
DELETE_TAG_SUCCESS_MESSAGE, DELETE_TAG_SUCCESS_MESSAGE,
DELETE_TAG_ERROR_MESSAGE, DELETE_TAG_ERROR_MESSAGE,
DELETE_TAGS_SUCCESS_MESSAGE, DELETE_TAGS_SUCCESS_MESSAGE,
DELETE_TAGS_ERROR_MESSAGE, DELETE_TAGS_ERROR_MESSAGE,
ADMIN_GARBAGE_COLLECTION_TIP,
} from '~/registry/explorer/constants'; } from '~/registry/explorer/constants';
import { tagsListResponse } from '../mock_data'; import { tagsListResponse } from '../mock_data';
import { GlModal } from '../stubs'; import { GlModal } from '../stubs';
...@@ -18,6 +19,7 @@ import { $toast } from '../../shared/mocks'; ...@@ -18,6 +19,7 @@ import { $toast } from '../../shared/mocks';
describe('Details Page', () => { describe('Details Page', () => {
let wrapper; let wrapper;
let dispatchSpy; let dispatchSpy;
let store;
const findDeleteModal = () => wrapper.find(GlModal); const findDeleteModal = () => wrapper.find(GlModal);
const findPagination = () => wrapper.find(GlPagination); const findPagination = () => wrapper.find(GlPagination);
...@@ -30,6 +32,7 @@ describe('Details Page', () => { ...@@ -30,6 +32,7 @@ describe('Details Page', () => {
const findAllCheckboxes = () => wrapper.findAll('.js-row-checkbox'); const findAllCheckboxes = () => wrapper.findAll('.js-row-checkbox');
const findCheckedCheckboxes = () => findAllCheckboxes().filter(c => c.attributes('checked')); const findCheckedCheckboxes = () => findAllCheckboxes().filter(c => c.attributes('checked'));
const findFirsTagColumn = () => wrapper.find('.js-tag-column'); const findFirsTagColumn = () => wrapper.find('.js-tag-column');
const findAlert = () => wrapper.find(GlAlert);
const routeId = window.btoa(JSON.stringify({ name: 'foo', tags_path: 'bar' })); const routeId = window.btoa(JSON.stringify({ name: 'foo', tags_path: 'bar' }));
...@@ -55,6 +58,7 @@ describe('Details Page', () => { ...@@ -55,6 +58,7 @@ describe('Details Page', () => {
}; };
beforeEach(() => { beforeEach(() => {
store = createStore();
dispatchSpy = jest.spyOn(store, 'dispatch'); dispatchSpy = jest.spyOn(store, 'dispatch');
store.dispatch('receiveTagsListSuccess', tagsListResponse); store.dispatch('receiveTagsListSuccess', tagsListResponse);
jest.spyOn(Tracking, 'event'); jest.spyOn(Tracking, 'event');
...@@ -62,6 +66,7 @@ describe('Details Page', () => { ...@@ -62,6 +66,7 @@ describe('Details Page', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('when isLoading is true', () => { describe('when isLoading is true', () => {
...@@ -328,25 +333,9 @@ describe('Details Page', () => { ...@@ -328,25 +333,9 @@ describe('Details Page', () => {
}); });
// itemsToBeDeleted is not represented in the DOM, is used as parking variable between selected and deleted items // itemsToBeDeleted is not represented in the DOM, is used as parking variable between selected and deleted items
expect(wrapper.vm.itemsToBeDeleted).toEqual([]); expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
expect(wrapper.vm.selectedItems).toEqual([]);
expect(findCheckedCheckboxes()).toHaveLength(0); expect(findCheckedCheckboxes()).toHaveLength(0);
}); });
it('show success toast on successful delete', () => {
return wrapper.vm.handleSingleDelete(0).then(() => {
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAG_SUCCESS_MESSAGE, {
type: 'success',
});
});
});
it('show error toast on erred delete', () => {
dispatchSpy.mockRejectedValue();
return wrapper.vm.handleSingleDelete(0).then(() => {
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAG_ERROR_MESSAGE, {
type: 'error',
});
});
});
}); });
describe('when multiple elements are selected', () => { describe('when multiple elements are selected', () => {
...@@ -365,34 +354,121 @@ describe('Details Page', () => { ...@@ -365,34 +354,121 @@ describe('Details Page', () => {
expect(wrapper.vm.itemsToBeDeleted).toEqual([]); expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
expect(findCheckedCheckboxes()).toHaveLength(0); expect(findCheckedCheckboxes()).toHaveLength(0);
}); });
});
});
it('tracks cancel_delete when cancel event is emitted', () => {
const deleteModal = findDeleteModal();
deleteModal.vm.$emit('cancel');
return wrapper.vm.$nextTick().then(() => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'cancel_delete', {
label: 'registry_tag_delete',
});
});
});
});
it('show success toast on successful delete', () => { describe('Delete alert', () => {
return wrapper.vm.handleMultipleDelete(0).then(() => { const config = {
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAGS_SUCCESS_MESSAGE, { garbageCollectionHelpPagePath: 'foo',
type: 'success', };
describe('when the user is an admin', () => {
beforeEach(() => {
store.commit(SET_INITIAL_STATE, { ...config, isAdmin: true });
}); });
afterEach(() => {
store.commit(SET_INITIAL_STATE, config);
});
describe.each`
deleteType | successTitle | errorTitle
${'handleSingleDelete'} | ${DELETE_TAG_SUCCESS_MESSAGE} | ${DELETE_TAG_ERROR_MESSAGE}
${'handleMultipleDelete'} | ${DELETE_TAGS_SUCCESS_MESSAGE} | ${DELETE_TAGS_ERROR_MESSAGE}
`('behaves correctly on $deleteType', ({ deleteType, successTitle, errorTitle }) => {
describe('when delete is successful', () => {
beforeEach(() => {
dispatchSpy.mockResolvedValue();
mountComponent();
return wrapper.vm[deleteType]('foo');
}); });
it('alert exists', () => {
expect(findAlert().exists()).toBe(true);
}); });
it('show error toast on erred delete', () => { it('alert body contains admin tip', () => {
expect(
findAlert()
.text()
.replace(/\s\s+/gm, ' '),
).toBe(ADMIN_GARBAGE_COLLECTION_TIP.replace(/%{\w+}/gm, ''));
});
it('alert body contains link', () => {
const alertLink = findAlert().find(GlLink);
expect(alertLink.exists()).toBe(true);
expect(alertLink.attributes('href')).toBe(config.garbageCollectionHelpPagePath);
});
it('alert title is appropriate', () => {
expect(findAlert().attributes('title')).toBe(successTitle);
});
});
describe('when delete is not successful', () => {
beforeEach(() => {
mountComponent();
dispatchSpy.mockRejectedValue(); dispatchSpy.mockRejectedValue();
return wrapper.vm.handleMultipleDelete(0).then(() => { return wrapper.vm[deleteType]('foo');
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAGS_ERROR_MESSAGE, {
type: 'error',
}); });
it('alert exist and text is appropriate', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(errorTitle);
}); });
}); });
}); });
}); });
it('tracks cancel_delete when cancel event is emitted', () => { describe.each`
const deleteModal = findDeleteModal(); deleteType | successTitle | errorTitle
deleteModal.vm.$emit('cancel'); ${'handleSingleDelete'} | ${DELETE_TAG_SUCCESS_MESSAGE} | ${DELETE_TAG_ERROR_MESSAGE}
return wrapper.vm.$nextTick().then(() => { ${'handleMultipleDelete'} | ${DELETE_TAGS_SUCCESS_MESSAGE} | ${DELETE_TAGS_ERROR_MESSAGE}
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'cancel_delete', { `(
label: 'registry_tag_delete', 'when the user is not an admin alert behaves correctly on $deleteType',
({ deleteType, successTitle, errorTitle }) => {
beforeEach(() => {
store.commit('SET_INITIAL_STATE', { ...config });
}); });
describe('when delete is successful', () => {
beforeEach(() => {
dispatchSpy.mockResolvedValue();
mountComponent();
return wrapper.vm[deleteType]('foo');
});
it('alert exist and text is appropriate', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(successTitle);
});
});
describe('when delete is not successful', () => {
beforeEach(() => {
mountComponent();
dispatchSpy.mockRejectedValue();
return wrapper.vm[deleteType]('foo');
});
it('alert exist and text is appropriate', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(errorTitle);
}); });
}); });
},
);
}); });
}); });
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import component from '~/registry/explorer/pages/index.vue'; import component from '~/registry/explorer/pages/index.vue';
import store from '~/registry/explorer/stores/'; import store from '~/registry/explorer/stores/';
describe('List Page', () => { describe('List Page', () => {
let wrapper; let wrapper;
let dispatchSpy;
const findRouterView = () => wrapper.find({ ref: 'router-view' }); const findRouterView = () => wrapper.find({ ref: 'router-view' });
const findAlert = () => wrapper.find(GlAlert);
const findLink = () => wrapper.find(GlLink);
const mountComponent = () => { const mountComponent = () => {
wrapper = shallowMount(component, { wrapper = shallowMount(component, {
store, store,
stubs: { stubs: {
RouterView: true, RouterView: true,
GlSprintf,
}, },
}); });
}; };
beforeEach(() => { beforeEach(() => {
dispatchSpy = jest.spyOn(store, 'dispatch');
mountComponent(); mountComponent();
}); });
it('has a router view', () => { it('has a router view', () => {
expect(findRouterView().exists()).toBe(true); expect(findRouterView().exists()).toBe(true);
}); });
describe('garbageCollectionTip alert', () => {
beforeEach(() => {
store.dispatch('setInitialState', { isAdmin: true, garbageCollectionHelpPagePath: 'foo' });
store.dispatch('setShowGarbageCollectionTip', true);
});
afterEach(() => {
store.dispatch('setInitialState', {});
store.dispatch('setShowGarbageCollectionTip', false);
});
it('is visible when the user is an admin and the user performed a delete action', () => {
expect(findAlert().exists()).toBe(true);
});
it('on dismiss disappears ', () => {
findAlert().vm.$emit('dismiss');
expect(dispatchSpy).toHaveBeenCalledWith('setShowGarbageCollectionTip', false);
return wrapper.vm.$nextTick().then(() => {
expect(findAlert().exists()).toBe(false);
});
});
it('contains a link to the docs', () => {
const link = findLink();
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(store.state.config.garbageCollectionHelpPagePath);
});
});
}); });
...@@ -191,7 +191,10 @@ describe('Actions RegistryExplorer Store', () => { ...@@ -191,7 +191,10 @@ describe('Actions RegistryExplorer Store', () => {
{ {
tagsPagination: {}, tagsPagination: {},
}, },
[{ type: types.SET_MAIN_LOADING, payload: true }], [
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[ [
{ {
type: 'setShowGarbageCollectionTip', type: 'setShowGarbageCollectionTip',
...@@ -220,8 +223,7 @@ describe('Actions RegistryExplorer Store', () => { ...@@ -220,8 +223,7 @@ describe('Actions RegistryExplorer Store', () => {
{ type: types.SET_MAIN_LOADING, payload: false }, { type: types.SET_MAIN_LOADING, payload: false },
], ],
[], [],
done, ).catch(() => done());
);
}); });
}); });
...@@ -241,7 +243,10 @@ describe('Actions RegistryExplorer Store', () => { ...@@ -241,7 +243,10 @@ describe('Actions RegistryExplorer Store', () => {
{ {
tagsPagination: {}, tagsPagination: {},
}, },
[{ type: types.SET_MAIN_LOADING, payload: true }], [
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[ [
{ {
type: 'setShowGarbageCollectionTip', type: 'setShowGarbageCollectionTip',
...@@ -273,8 +278,7 @@ describe('Actions RegistryExplorer Store', () => { ...@@ -273,8 +278,7 @@ describe('Actions RegistryExplorer Store', () => {
{ type: types.SET_MAIN_LOADING, payload: false }, { type: types.SET_MAIN_LOADING, payload: false },
], ],
[], [],
done, ).catch(() => done());
);
}); });
}); });
...@@ -311,9 +315,7 @@ describe('Actions RegistryExplorer Store', () => { ...@@ -311,9 +315,7 @@ describe('Actions RegistryExplorer Store', () => {
{ type: types.SET_MAIN_LOADING, payload: false }, { type: types.SET_MAIN_LOADING, payload: false },
], ],
[], [],
).catch(() => { ).catch(() => done());
done();
});
}); });
}); });
}); });
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