table_registry_spec.js 12.4 KB
Newer Older
1
import Vue from 'vue';
2 3
import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
4 5 6
import createFlash from '~/flash';
import Tracking from '~/tracking';
import tableRegistry from '~/registry/components/table_registry.vue';
7
import { repoPropsData } from '../mock_data';
8
import * as getters from '~/registry/stores/getters';
9

10 11
jest.mock('~/flash');

12 13
const [firstImage, secondImage] = repoPropsData.list;

14 15 16 17
const localVue = createLocalVue();

localVue.use(Vuex);

18 19
describe('table registry', () => {
  let wrapper;
20
  let store;
21

22 23 24 25 26 27
  const findSelectAllCheckbox = (w = wrapper) => w.find('.js-select-all-checkbox > input');
  const findSelectCheckboxes = (w = wrapper) => w.findAll('.js-select-checkbox > input');
  const findDeleteButton = (w = wrapper) => w.find({ ref: 'bulkDeleteButton' });
  const findDeleteButtonsRow = (w = wrapper) => w.findAll('.js-delete-registry-row');
  const findPagination = (w = wrapper) => w.find('.js-registry-pagination');
  const findDeleteModal = (w = wrapper) => w.find({ ref: 'deleteModal' });
28
  const findImageId = (w = wrapper) => w.find({ ref: 'imageId' });
29 30
  const bulkDeletePath = 'path';

31 32
  const mountWithStore = config => mount(tableRegistry, { ...config, store, localVue });

33 34 35 36
  beforeEach(() => {
    // This is needed due to  console.error called by vue to emit a warning that stop the tests
    // see  https://github.com/vuejs/vue-test-utils/issues/532
    Vue.config.silent = true;
37 38 39 40 41 42 43 44 45

    store = new Vuex.Store({
      state: {
        isDeleteDisabled: false,
      },
      getters,
    });

    wrapper = mountWithStore({
46 47
      propsData: {
        repo: repoPropsData,
48
        canDeleteRepo: true,
49 50 51 52 53 54
      },
    });
  });

  afterEach(() => {
    Vue.config.silent = false;
55
    wrapper.destroy();
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
  });

  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.timeFormated(repoPropsData.list[0].createdAt));
    });
72 73 74 75 76 77 78 79 80

    it('should have a label called Image ID', () => {
      const label = findImageId();
      expect(label.element).toMatchInlineSnapshot(`
        <th>
          Image ID
        </th>
      `);
    });
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
  });

  describe('multi select', () => {
    it('selecting a row should enable delete button', done => {
      const deleteBtn = findDeleteButton(wrapper);
      const checkboxes = findSelectCheckboxes(wrapper);

      expect(deleteBtn.attributes('disabled')).toBe('disabled');

      checkboxes.at(0).trigger('click');
      Vue.nextTick(() => {
        expect(deleteBtn.attributes('disabled')).toEqual(undefined);
        done();
      });
    });

    it('selecting all checkbox should select all rows and enable delete button', done => {
      const selectAll = findSelectAllCheckbox(wrapper);
      const checkboxes = findSelectCheckboxes(wrapper);
      selectAll.trigger('click');

      Vue.nextTick(() => {
        const checked = checkboxes.filter(w => w.element.checked);
        expect(checked.length).toBe(checkboxes.length);
        done();
      });
    });

    it('deselecting select all checkbox should deselect all rows and disable delete button', done => {
      const checkboxes = findSelectCheckboxes(wrapper);
      const selectAll = findSelectAllCheckbox(wrapper);
      selectAll.trigger('click');
      selectAll.trigger('click');

      Vue.nextTick(() => {
        const checked = checkboxes.filter(w => !w.element.checked);
        expect(checked.length).toBe(checkboxes.length);
        done();
      });
    });

    it('should delete multiple items when multiple items are selected', done => {
      const multiDeleteItems = jest.fn().mockResolvedValue();
      wrapper.setMethods({ multiDeleteItems });
      const selectAll = findSelectAllCheckbox(wrapper);
      selectAll.trigger('click');

      Vue.nextTick(() => {
        const deleteBtn = findDeleteButton(wrapper);
130
        expect(wrapper.vm.selectedItems).toEqual([0, 1]);
131
        expect(deleteBtn.attributes('disabled')).toEqual(undefined);
132
        wrapper.setData({ itemsToBeDeleted: [...wrapper.vm.selectedItems] });
133 134 135
        wrapper.vm.handleMultipleDelete();

        Vue.nextTick(() => {
136
          expect(wrapper.vm.selectedItems).toEqual([]);
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
          expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
          expect(wrapper.vm.multiDeleteItems).toHaveBeenCalledWith({
            path: bulkDeletePath,
            items: [firstImage.tag, secondImage.tag],
          });
          done();
        });
      });
    });

    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();
157
      expect(createFlash).toHaveBeenCalled();
158 159 160 161 162
    });
  });

  describe('delete registry', () => {
    beforeEach(() => {
163
      wrapper.setData({ selectedItems: [0] });
164 165 166 167 168
    });

    it('should be possible to delete a registry', () => {
      const deleteBtn = findDeleteButton(wrapper);
      const deleteBtns = findDeleteButtonsRow(wrapper);
169
      expect(wrapper.vm.selectedItems).toEqual([0]);
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
      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(wrapper);
      const deleteSingleItem = jest.fn();
      const deleteItem = jest.fn().mockResolvedValue();
      wrapper.setMethods({ deleteSingleItem, deleteItem });
      deleteBtns.at(0).trigger('click');
      expect(wrapper.vm.deleteSingleItem).toHaveBeenCalledWith(0);
      wrapper.vm.handleSingleDelete(1);
      expect(wrapper.vm.deleteItem).toHaveBeenCalledWith(1);
    });
  });

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
  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 muultiDelete', () => {
      wrapper.setData({ itemsToBeDeleted: [0, 1, 2] });
      wrapper.vm.onDeletionConfirmed();

      expect(wrapper.vm.handleMultipleDelete).toHaveBeenCalled();
      expect(wrapper.vm.handleSingleDelete).not.toHaveBeenCalled();
    });
  });

208 209 210 211 212 213 214 215 216 217 218
  describe('pagination', () => {
    const repo = {
      repoPropsData,
      pagination: {
        total: 20,
        perPage: 2,
        nextPage: 2,
      },
    };

    beforeEach(() => {
219
      wrapper = mount(tableRegistry, {
220 221 222 223 224 225 226
        propsData: {
          repo,
        },
      });
    });

    it('should exist', () => {
227
      const pagination = findPagination(wrapper);
228 229 230
      expect(pagination.exists()).toBe(true);
    });
    it('should be visible when pagination is needed', () => {
231
      const pagination = findPagination(wrapper);
232
      expect(pagination.isVisible()).toBe(true);
233
      wrapper.setProps({
234 235 236 237 238 239 240
        repo: {
          pagination: {
            total: 0,
            perPage: 10,
          },
        },
      });
241
      expect(wrapper.vm.shouldRenderPagination).toBe(false);
242 243 244
    });
    it('should have a change function that update the list when run', () => {
      const fetchList = jest.fn().mockResolvedValue();
245 246 247
      wrapper.setMethods({ fetchList });
      wrapper.vm.onPageChange(1);
      expect(wrapper.vm.fetchList).toHaveBeenCalledWith({ repo, page: 1 });
248 249 250 251 252
    });
  });

  describe('modal content', () => {
    it('should show the singular title and image name when deleting a single image', () => {
253 254
      wrapper.setData({ selectedItems: [1, 2, 3] });
      wrapper.vm.deleteSingleItem(0);
255
      expect(wrapper.vm.modalAction).toBe('Remove tag');
256 257 258 259
      expect(wrapper.vm.modalDescription).toContain(firstImage.tag);
    });

    it('should show the plural title and image count when deleting more than one image', () => {
260 261
      wrapper.setData({ selectedItems: [1, 2] });
      wrapper.vm.deleteMultipleItems();
262

263
      expect(wrapper.vm.modalAction).toBe('Remove tags');
264
      expect(wrapper.vm.modalDescription).toContain('<b>2</b> tags');
265 266
    });
  });
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303

  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(wrapper);
      expect(selectAll.exists()).toBe(false);
    });

    it('should not render any select checkbox', () => {
      const selects = findSelectCheckboxes(wrapper);
      expect(selects.length).toBe(0);
    });

    it('should not render delete registry button', () => {
      const deleteBtn = findDeleteButton(wrapper);
      expect(deleteBtn.exists()).toBe(false);
    });

    it('should not render delete row button', () => {
      const deleteBtns = findDeleteButtonsRow(wrapper);
      expect(deleteBtns.length).toBe(0);
    });
  });
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382

  describe('event tracking', () => {
    const mockPageName = 'mock_page';

    beforeEach(() => {
      jest.spyOn(Tracking, 'event');
      wrapper.vm.handleSingleDelete = jest.fn();
      wrapper.vm.handleMultipleDelete = jest.fn();
      document.body.dataset.page = mockPageName;
    });

    afterEach(() => {
      document.body.dataset.page = null;
    });

    describe('single tag delete', () => {
      beforeEach(() => {
        wrapper.setData({ itemsToBeDeleted: [0] });
      });

      it('send an event when delete button is clicked', () => {
        const deleteBtn = findDeleteButtonsRow();
        deleteBtn.at(0).trigger('click');
        expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'click_button', {
          label: 'registry_tag_delete',
          property: 'foo',
        });
      });
      it('send an event when cancel is pressed on modal', () => {
        const deleteModal = findDeleteModal();
        deleteModal.vm.$emit('cancel');
        expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'cancel_delete', {
          label: 'registry_tag_delete',
          property: 'foo',
        });
      });
      it('send an event when confirm is clicked on modal', () => {
        const deleteModal = findDeleteModal();
        deleteModal.vm.$emit('ok');

        expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'confirm_delete', {
          label: 'registry_tag_delete',
          property: 'foo',
        });
      });
    });
    describe('bulk tag delete', () => {
      beforeEach(() => {
        const items = [0, 1, 2];
        wrapper.setData({ itemsToBeDeleted: items, selectedItems: items });
      });

      it('send an event when delete button is clicked', () => {
        const deleteBtn = findDeleteButton();
        deleteBtn.vm.$emit('click');
        expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'click_button', {
          label: 'bulk_registry_tag_delete',
          property: 'foo',
        });
      });
      it('send an event when cancel is pressed on modal', () => {
        const deleteModal = findDeleteModal();
        deleteModal.vm.$emit('cancel');
        expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'cancel_delete', {
          label: 'bulk_registry_tag_delete',
          property: 'foo',
        });
      });
      it('send an event when confirm is clicked on modal', () => {
        const deleteModal = findDeleteModal();
        deleteModal.vm.$emit('ok');

        expect(Tracking.event).toHaveBeenCalledWith(mockPageName, 'confirm_delete', {
          label: 'bulk_registry_tag_delete',
          property: 'foo',
        });
      });
    });
  });
383
});