import { mount } from '@vue/test-utils';
import { GlTable, GlPagination, GlSkeletonLoader } from '@gitlab/ui';
import Tracking from '~/tracking';
import stubChildren from 'helpers/stub_children';
import component from '~/registry/explorer/pages/details.vue';
import store from '~/registry/explorer/stores/';
import { SET_MAIN_LOADING } from '~/registry/explorer/stores/mutation_types/';
import {
DELETE_TAG_SUCCESS_MESSAGE,
DELETE_TAG_ERROR_MESSAGE,
DELETE_TAGS_SUCCESS_MESSAGE,
DELETE_TAGS_ERROR_MESSAGE,
} from '~/registry/explorer/constants';
import { tagsListResponse } from '../mock_data';
import { GlModal } from '../stubs';
import { $toast } from '../../shared/mocks';
describe('Details Page', () => {
let wrapper;
let dispatchSpy;
const findDeleteModal = () => wrapper.find(GlModal);
const findPagination = () => wrapper.find(GlPagination);
const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader);
const findMainCheckbox = () => wrapper.find({ ref: 'mainCheckbox' });
const findFirstRowItem = ref => wrapper.find({ ref });
const findBulkDeleteButton = () => wrapper.find({ ref: 'bulkDeleteButton' });
// findAll and refs seems to no work falling back to class
const findAllDeleteButtons = () => wrapper.findAll('.js-delete-registry');
const findAllCheckboxes = () => wrapper.findAll('.js-row-checkbox');
const findCheckedCheckboxes = () => findAllCheckboxes().filter(c => c.attributes('checked'));
const findFirsTagColumn = () => wrapper.find('.js-tag-column');
const routeId = window.btoa(JSON.stringify({ name: 'foo', tags_path: 'bar' }));
const mountComponent = options => {
wrapper = mount(component, {
store,
stubs: {
...stubChildren(component),
GlModal,
GlSprintf: false,
GlTable,
},
mocks: {
$route: {
params: {
id: routeId,
},
},
$toast,
},
...options,
});
};
beforeEach(() => {
dispatchSpy = jest.spyOn(store, 'dispatch');
store.dispatch('receiveTagsListSuccess', tagsListResponse);
jest.spyOn(Tracking, 'event');
});
afterEach(() => {
wrapper.destroy();
});
describe('when isLoading is true', () => {
beforeEach(() => {
mountComponent();
store.dispatch('receiveTagsListSuccess', { ...tagsListResponse, data: [] });
store.commit(SET_MAIN_LOADING, true);
});
afterAll(() => store.commit(SET_MAIN_LOADING, false));
it('has a skeleton loader', () => {
expect(findSkeletonLoader().exists()).toBe(true);
});
it('does not have list items', () => {
expect(findFirstRowItem('rowCheckbox').exists()).toBe(false);
});
it('does not show pagination', () => {
expect(findPagination().exists()).toBe(false);
});
});
describe('table', () => {
beforeEach(() => {
mountComponent();
});
it.each([
'rowCheckbox',
'rowName',
'rowShortRevision',
'rowSize',
'rowTime',
'singleDeleteButton',
])('%s exist in the table', element => {
expect(findFirstRowItem(element).exists()).toBe(true);
});
describe('header checkbox', () => {
beforeEach(() => {
mountComponent();
});
it('exists', () => {
expect(findMainCheckbox().exists()).toBe(true);
});
it('if selected set selectedItem and allSelected', () => {
findMainCheckbox().vm.$emit('change');
return wrapper.vm.$nextTick().then(() => {
expect(findMainCheckbox().attributes('checked')).toBeTruthy();
expect(findCheckedCheckboxes()).toHaveLength(store.state.tags.length);
});
});
it('if deselect unset selectedItem and allSelected', () => {
wrapper.setData({ selectedItems: [1, 2], selectAllChecked: true });
findMainCheckbox().vm.$emit('change');
return wrapper.vm.$nextTick().then(() => {
expect(findMainCheckbox().attributes('checked')).toBe(undefined);
expect(findCheckedCheckboxes()).toHaveLength(0);
});
});
});
describe('row checkbox', () => {
beforeEach(() => {
mountComponent();
});
it('if selected adds item to selectedItems', () => {
findFirstRowItem('rowCheckbox').vm.$emit('change');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.selectedItems).toEqual([1]);
expect(findFirstRowItem('rowCheckbox').attributes('checked')).toBeTruthy();
});
});
it('if deselect remove index from selectedItems', () => {
wrapper.setData({ selectedItems: [1] });
findFirstRowItem('rowCheckbox').vm.$emit('change');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.selectedItems.length).toBe(0);
expect(findFirstRowItem('rowCheckbox').attributes('checked')).toBe(undefined);
});
});
});
describe('header delete button', () => {
beforeEach(() => {
mountComponent();
});
it('exists', () => {
expect(findBulkDeleteButton().exists()).toBe(true);
});
it('is disabled if no item is selected', () => {
expect(findBulkDeleteButton().attributes('disabled')).toBe('true');
});
it('is enabled if at least one item is selected', () => {
wrapper.setData({ selectedItems: [1] });
return wrapper.vm.$nextTick().then(() => {
expect(findBulkDeleteButton().attributes('disabled')).toBeFalsy();
});
});
describe('on click', () => {
it('when one item is selected', () => {
wrapper.setData({ selectedItems: [1] });
findBulkDeleteButton().vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(findDeleteModal().html()).toContain(
'You are about to remove foo. Are you sure?',
);
expect(GlModal.methods.show).toHaveBeenCalled();
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'registry_tag_delete',
});
});
});
it('when multiple items are selected', () => {
wrapper.setData({ selectedItems: [0, 1] });
findBulkDeleteButton().vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(findDeleteModal().html()).toContain(
'You are about to remove 2 tags. Are you sure?',
);
expect(GlModal.methods.show).toHaveBeenCalled();
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'bulk_registry_tag_delete',
});
});
});
});
});
describe('row delete button', () => {
beforeEach(() => {
mountComponent();
});
it('exists', () => {
expect(
findAllDeleteButtons()
.at(0)
.exists(),
).toBe(true);
});
it('is disabled if the item has no destroy_path', () => {
expect(
findAllDeleteButtons()
.at(1)
.attributes('disabled'),
).toBe('true');
});
it('on click', () => {
findAllDeleteButtons()
.at(0)
.vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(findDeleteModal().html()).toContain(
'You are about to remove bar. Are you sure?',
);
expect(GlModal.methods.show).toHaveBeenCalled();
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'registry_tag_delete',
});
});
});
});
describe('tag cell', () => {
describe('on desktop viewport', () => {
beforeEach(() => {
mountComponent();
});
it('has class w-25', () => {
expect(findFirsTagColumn().classes()).toContain('w-25');
});
});
describe('on mobile viewport', () => {
beforeEach(() => {
mountComponent({
data() {
return { isDesktop: false };
},
});
});
it('does not has class w-25', () => {
expect(findFirsTagColumn().classes()).not.toContain('w-25');
});
});
});
});
describe('pagination', () => {
beforeEach(() => {
mountComponent();
});
it('exists', () => {
expect(findPagination().exists()).toBe(true);
});
it('is wired to the correct pagination props', () => {
const pagination = findPagination();
expect(pagination.props('perPage')).toBe(store.state.tagsPagination.perPage);
expect(pagination.props('totalItems')).toBe(store.state.tagsPagination.total);
expect(pagination.props('value')).toBe(store.state.tagsPagination.page);
});
it('fetch the data from the API when the v-model changes', () => {
dispatchSpy.mockResolvedValue();
wrapper.setData({ currentPage: 2 });
expect(store.dispatch).toHaveBeenCalledWith('requestTagsList', {
params: wrapper.vm.$route.params.id,
pagination: { page: 2 },
});
});
});
describe('modal', () => {
beforeEach(() => {
mountComponent();
});
it('exists', () => {
expect(findDeleteModal().exists()).toBe(true);
});
describe('when ok event is emitted', () => {
beforeEach(() => {
dispatchSpy.mockResolvedValue();
});
it('tracks confirm_delete', () => {
const deleteModal = findDeleteModal();
deleteModal.vm.$emit('ok');
return wrapper.vm.$nextTick().then(() => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'confirm_delete', {
label: 'registry_tag_delete',
});
});
});
describe('when only one element is selected', () => {
it('execute the delete and remove selection', () => {
wrapper.setData({ itemsToBeDeleted: [0] });
findDeleteModal().vm.$emit('ok');
expect(store.dispatch).toHaveBeenCalledWith('requestDeleteTag', {
tag: store.state.tags[0],
params: wrapper.vm.$route.params.id,
});
// itemsToBeDeleted is not represented in the DOM, is used as parking variable between selected and deleted items
expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
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', () => {
beforeEach(() => {
wrapper.setData({ itemsToBeDeleted: [0, 1] });
});
it('execute the delete and remove selection', () => {
findDeleteModal().vm.$emit('ok');
expect(store.dispatch).toHaveBeenCalledWith('requestDeleteTags', {
ids: store.state.tags.map(t => t.name),
params: wrapper.vm.$route.params.id,
});
// itemsToBeDeleted is not represented in the DOM, is used as parking variable between selected and deleted items
expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
expect(findCheckedCheckboxes()).toHaveLength(0);
});
it('show success toast on successful delete', () => {
return wrapper.vm.handleMultipleDelete(0).then(() => {
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAGS_SUCCESS_MESSAGE, {
type: 'success',
});
});
});
it('show error toast on erred delete', () => {
dispatchSpy.mockRejectedValue();
return wrapper.vm.handleMultipleDelete(0).then(() => {
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAGS_ERROR_MESSAGE, {
type: 'error',
});
});
});
});
});
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',
});
});
});
});
});