373 lines
12 KiB
JavaScript
373 lines
12 KiB
JavaScript
import Vuex from 'vuex';
|
|
import { mount, createLocalVue } from '@vue/test-utils';
|
|
import createFlash from '~/flash';
|
|
import Tracking from '~/tracking';
|
|
import tableRegistry from '~/registry/list/components/table_registry.vue';
|
|
import { repoPropsData } from '../mock_data';
|
|
import * as getters from '~/registry/list/stores/getters';
|
|
|
|
jest.mock('~/flash');
|
|
|
|
const [firstImage, secondImage] = repoPropsData.list;
|
|
|
|
const localVue = createLocalVue();
|
|
|
|
localVue.use(Vuex);
|
|
|
|
describe('table registry', () => {
|
|
let wrapper;
|
|
let store;
|
|
|
|
const findSelectAllCheckbox = () => wrapper.find('.js-select-all-checkbox > input');
|
|
const findSelectCheckboxes = () => wrapper.findAll('.js-select-checkbox > input');
|
|
const findDeleteButton = () => wrapper.find({ ref: 'bulkDeleteButton' });
|
|
const findDeleteButtonsRow = () => wrapper.findAll('.js-delete-registry-row');
|
|
const findPagination = () => wrapper.find('.js-registry-pagination');
|
|
const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' });
|
|
const findImageId = () => wrapper.find({ ref: 'imageId' });
|
|
const bulkDeletePath = 'path';
|
|
|
|
const mountWithStore = config =>
|
|
mount(tableRegistry, {
|
|
...config,
|
|
store,
|
|
localVue,
|
|
});
|
|
|
|
beforeEach(() => {
|
|
store = new Vuex.Store({
|
|
state: {
|
|
isDeleteDisabled: false,
|
|
},
|
|
getters,
|
|
});
|
|
|
|
wrapper = mountWithStore({
|
|
propsData: {
|
|
repo: repoPropsData,
|
|
canDeleteRepo: true,
|
|
},
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
wrapper.destroy();
|
|
});
|
|
|
|
describe('rendering', () => {
|
|
it('should render a table with the registry list', () => {
|
|
expect(wrapper.findAll('.registry-image-row').length).toEqual(repoPropsData.list.length);
|
|
});
|
|
|
|
it('should render registry tag', () => {
|
|
const tds = wrapper.findAll('.registry-image-row td');
|
|
expect(tds.at(0).classes()).toContain('check');
|
|
expect(tds.at(1).html()).toContain(repoPropsData.list[0].tag);
|
|
expect(tds.at(2).html()).toContain(repoPropsData.list[0].shortRevision);
|
|
expect(tds.at(3).html()).toContain(repoPropsData.list[0].layers);
|
|
expect(tds.at(3).html()).toContain(repoPropsData.list[0].size);
|
|
expect(tds.at(4).html()).toContain(wrapper.vm.timeFormatted(repoPropsData.list[0].createdAt));
|
|
});
|
|
|
|
it('should have a label called Image ID', () => {
|
|
const label = findImageId();
|
|
expect(label.element).toMatchInlineSnapshot(`
|
|
<th>
|
|
Image ID
|
|
</th>
|
|
`);
|
|
});
|
|
});
|
|
|
|
describe('multi select', () => {
|
|
it('selecting a row should enable delete button', () => {
|
|
const deleteBtn = findDeleteButton();
|
|
const checkboxes = findSelectCheckboxes();
|
|
|
|
expect(deleteBtn.attributes('disabled')).toBe('disabled');
|
|
|
|
checkboxes.at(0).trigger('click');
|
|
return wrapper.vm.$nextTick().then(() => {
|
|
expect(deleteBtn.attributes('disabled')).toEqual(undefined);
|
|
});
|
|
});
|
|
|
|
it('selecting all checkbox should select all rows and enable delete button', () => {
|
|
const selectAll = findSelectAllCheckbox();
|
|
selectAll.trigger('click');
|
|
|
|
return wrapper.vm.$nextTick().then(() => {
|
|
const checkboxes = findSelectCheckboxes();
|
|
const checked = checkboxes.filter(w => w.element.checked);
|
|
expect(checked.length).toBe(checkboxes.length);
|
|
});
|
|
});
|
|
|
|
it('deselecting select all checkbox should deselect all rows and disable delete button', () => {
|
|
const checkboxes = findSelectCheckboxes();
|
|
const selectAll = findSelectAllCheckbox();
|
|
selectAll.trigger('click');
|
|
selectAll.trigger('click');
|
|
|
|
return wrapper.vm.$nextTick().then(() => {
|
|
const checked = checkboxes.filter(w => !w.element.checked);
|
|
expect(checked.length).toBe(checkboxes.length);
|
|
});
|
|
});
|
|
|
|
it('should delete multiple items when multiple items are selected', () => {
|
|
const multiDeleteItems = jest.fn().mockResolvedValue();
|
|
wrapper.setMethods({ multiDeleteItems });
|
|
|
|
return wrapper.vm
|
|
.$nextTick()
|
|
.then(() => {
|
|
const selectAll = findSelectAllCheckbox();
|
|
selectAll.trigger('click');
|
|
return wrapper.vm.$nextTick();
|
|
})
|
|
.then(() => {
|
|
const deleteBtn = findDeleteButton();
|
|
expect(wrapper.vm.selectedItems).toEqual([0, 1]);
|
|
expect(deleteBtn.attributes('disabled')).toEqual(undefined);
|
|
wrapper.setData({ itemsToBeDeleted: [...wrapper.vm.selectedItems] });
|
|
wrapper.vm.handleMultipleDelete();
|
|
expect(wrapper.vm.selectedItems).toEqual([]);
|
|
expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
|
|
expect(wrapper.vm.multiDeleteItems).toHaveBeenCalledWith({
|
|
path: bulkDeletePath,
|
|
items: [firstImage.tag, secondImage.tag],
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should show an error message if bulkDeletePath is not set', () => {
|
|
const showError = jest.fn();
|
|
wrapper.setMethods({ showError });
|
|
wrapper.setProps({
|
|
repo: {
|
|
...repoPropsData,
|
|
tagsPath: null,
|
|
},
|
|
});
|
|
wrapper.vm.handleMultipleDelete();
|
|
expect(createFlash).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('delete registry', () => {
|
|
beforeEach(() => {
|
|
wrapper.setData({ selectedItems: [0] });
|
|
return wrapper.vm.$nextTick();
|
|
});
|
|
|
|
it('should be possible to delete a registry', () => {
|
|
const deleteBtn = findDeleteButton();
|
|
const deleteBtns = findDeleteButtonsRow();
|
|
expect(wrapper.vm.selectedItems).toEqual([0]);
|
|
expect(deleteBtn).toBeDefined();
|
|
expect(deleteBtn.attributes('disable')).toBe(undefined);
|
|
expect(deleteBtns.is('button')).toBe(true);
|
|
});
|
|
|
|
it('should allow deletion row by row', () => {
|
|
const deleteBtns = findDeleteButtonsRow();
|
|
const deleteSingleItem = jest.fn();
|
|
const deleteItem = jest.fn().mockResolvedValue();
|
|
wrapper.setMethods({ deleteSingleItem, deleteItem });
|
|
return wrapper.vm.$nextTick().then(() => {
|
|
deleteBtns.at(0).trigger('click');
|
|
expect(wrapper.vm.deleteSingleItem).toHaveBeenCalledWith(0);
|
|
wrapper.vm.handleSingleDelete(1);
|
|
expect(wrapper.vm.deleteItem).toHaveBeenCalledWith(1);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('modal event handlers', () => {
|
|
beforeEach(() => {
|
|
wrapper.vm.handleSingleDelete = jest.fn();
|
|
wrapper.vm.handleMultipleDelete = jest.fn();
|
|
});
|
|
it('on ok when one item is selected should call singleDelete', () => {
|
|
wrapper.setData({ itemsToBeDeleted: [0] });
|
|
wrapper.vm.onDeletionConfirmed();
|
|
|
|
expect(wrapper.vm.handleSingleDelete).toHaveBeenCalledWith(repoPropsData.list[0]);
|
|
expect(wrapper.vm.handleMultipleDelete).not.toHaveBeenCalled();
|
|
});
|
|
it('on ok when multiple items are selected should call multiDelete', () => {
|
|
wrapper.setData({ itemsToBeDeleted: [0, 1, 2] });
|
|
wrapper.vm.onDeletionConfirmed();
|
|
|
|
expect(wrapper.vm.handleMultipleDelete).toHaveBeenCalled();
|
|
expect(wrapper.vm.handleSingleDelete).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('pagination', () => {
|
|
const repo = {
|
|
repoPropsData,
|
|
pagination: {
|
|
total: 20,
|
|
perPage: 2,
|
|
nextPage: 2,
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
wrapper = mount(tableRegistry, {
|
|
propsData: {
|
|
repo,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should exist', () => {
|
|
const pagination = findPagination();
|
|
expect(pagination.exists()).toBe(true);
|
|
});
|
|
it('should be visible when pagination is needed', () => {
|
|
const pagination = findPagination();
|
|
expect(pagination.isVisible()).toBe(true);
|
|
wrapper.setProps({
|
|
repo: {
|
|
pagination: {
|
|
total: 0,
|
|
perPage: 10,
|
|
},
|
|
},
|
|
});
|
|
expect(wrapper.vm.shouldRenderPagination).toBe(false);
|
|
});
|
|
it('should have a change function that update the list when run', () => {
|
|
const fetchList = jest.fn().mockResolvedValue();
|
|
wrapper.setMethods({ fetchList });
|
|
wrapper.vm.onPageChange(1);
|
|
expect(wrapper.vm.fetchList).toHaveBeenCalledWith({ repo, page: 1 });
|
|
});
|
|
});
|
|
|
|
describe('modal content', () => {
|
|
it('should show the singular title and image name when deleting a single image', () => {
|
|
wrapper.setData({ selectedItems: [1, 2, 3] });
|
|
wrapper.vm.deleteSingleItem(0);
|
|
expect(wrapper.vm.modalAction).toBe('Remove tag');
|
|
expect(wrapper.vm.modalDescription).toContain(firstImage.tag);
|
|
});
|
|
|
|
it('should show the plural title and image count when deleting more than one image', () => {
|
|
wrapper.setData({ selectedItems: [1, 2] });
|
|
wrapper.vm.deleteMultipleItems();
|
|
|
|
expect(wrapper.vm.modalAction).toBe('Remove tags');
|
|
expect(wrapper.vm.modalDescription).toContain('<b>2</b> tags');
|
|
});
|
|
});
|
|
|
|
describe('disabled delete', () => {
|
|
beforeEach(() => {
|
|
store = new Vuex.Store({
|
|
state: {
|
|
isDeleteDisabled: true,
|
|
},
|
|
getters,
|
|
});
|
|
wrapper = mountWithStore({
|
|
propsData: {
|
|
repo: repoPropsData,
|
|
canDeleteRepo: false,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should not render select all', () => {
|
|
const selectAll = findSelectAllCheckbox();
|
|
expect(selectAll.exists()).toBe(false);
|
|
});
|
|
|
|
it('should not render any select checkbox', () => {
|
|
const selects = findSelectCheckboxes();
|
|
expect(selects.length).toBe(0);
|
|
});
|
|
|
|
it('should not render delete registry button', () => {
|
|
const deleteBtn = findDeleteButton();
|
|
expect(deleteBtn.exists()).toBe(false);
|
|
});
|
|
|
|
it('should not render delete row button', () => {
|
|
const deleteBtns = findDeleteButtonsRow();
|
|
expect(deleteBtns.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('event tracking', () => {
|
|
const testTrackingCall = (action, label = 'registry_tag_delete') => {
|
|
expect(Tracking.event).toHaveBeenCalledWith(undefined, action, { label, property: 'foo' });
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.spyOn(Tracking, 'event');
|
|
wrapper.vm.handleSingleDelete = jest.fn();
|
|
wrapper.vm.handleMultipleDelete = jest.fn();
|
|
});
|
|
|
|
describe('single tag delete', () => {
|
|
beforeEach(() => {
|
|
wrapper.setData({ itemsToBeDeleted: [0] });
|
|
return wrapper.vm.$nextTick();
|
|
});
|
|
|
|
it('send an event when delete button is clicked', () => {
|
|
const deleteBtn = findDeleteButtonsRow();
|
|
deleteBtn.at(0).trigger('click');
|
|
|
|
testTrackingCall('click_button');
|
|
});
|
|
|
|
it('send an event when cancel is pressed on modal', () => {
|
|
const deleteModal = findDeleteModal();
|
|
deleteModal.vm.$emit('cancel');
|
|
|
|
testTrackingCall('cancel_delete');
|
|
});
|
|
|
|
it('send an event when confirm is clicked on modal', () => {
|
|
const deleteModal = findDeleteModal();
|
|
deleteModal.vm.$emit('ok');
|
|
|
|
testTrackingCall('confirm_delete');
|
|
});
|
|
});
|
|
|
|
describe('bulk tag delete', () => {
|
|
beforeEach(() => {
|
|
const items = [0, 1, 2];
|
|
wrapper.setData({ itemsToBeDeleted: items, selectedItems: items });
|
|
return wrapper.vm.$nextTick();
|
|
});
|
|
|
|
it('send an event when delete button is clicked', () => {
|
|
const deleteBtn = findDeleteButton();
|
|
deleteBtn.vm.$emit('click');
|
|
|
|
testTrackingCall('click_button', 'bulk_registry_tag_delete');
|
|
});
|
|
|
|
it('send an event when cancel is pressed on modal', () => {
|
|
const deleteModal = findDeleteModal();
|
|
deleteModal.vm.$emit('cancel');
|
|
|
|
testTrackingCall('cancel_delete', 'bulk_registry_tag_delete');
|
|
});
|
|
|
|
it('send an event when confirm is clicked on modal', () => {
|
|
const deleteModal = findDeleteModal();
|
|
deleteModal.vm.$emit('ok');
|
|
|
|
testTrackingCall('confirm_delete', 'bulk_registry_tag_delete');
|
|
});
|
|
});
|
|
});
|
|
});
|