Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorThong Kuah <tkuah@gitlab.com>2019-08-16 02:57:38 +0300
committerThong Kuah <tkuah@gitlab.com>2019-08-16 02:57:38 +0300
commitf4ce990b0e2811cd2c3391fc5f11de990bc54f16 (patch)
tree34bfb064d8cdec92337d59f9904a64850b8a47d7 /spec
parentbd5c259a0bb94cb601a3c0f017c383731ac868e6 (diff)
parentf3de7855f90ed6785f546ed4831e3cc9d34c63ad (diff)
Merge branch '24705-multi-selection-for-delete-on-registry-page' into 'master'
Resolve "Multi selection for delete on registry page" Closes #24705 See merge request gitlab-org/gitlab-ce!30837
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb33
-rw-r--r--spec/features/container_registry_spec.rb10
-rw-r--r--spec/javascripts/registry/components/table_registry_spec.js175
-rw-r--r--spec/javascripts/registry/mock_data.js11
4 files changed, 196 insertions, 33 deletions
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
index ff35139ae2e..c6e063d8229 100644
--- a/spec/controllers/projects/registry/tags_controller_spec.rb
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -113,4 +113,37 @@ describe Projects::Registry::TagsController do
format: :json
end
end
+
+ describe 'POST bulk_destroy' do
+ context 'when user has access to registry' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when there is matching tag present' do
+ before do
+ stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.])
+ end
+
+ it 'makes it possible to delete tags in bulk' do
+ allow_any_instance_of(ContainerRegistry::Tag).to receive(:delete) { |*args| ContainerRegistry::Tag.delete(*args) }
+ expect(ContainerRegistry::Tag).to receive(:delete).exactly(2).times
+
+ bulk_destroy_tags(['rc1', 'test.'])
+ end
+ end
+ end
+
+ private
+
+ def bulk_destroy_tags(names)
+ post :bulk_destroy, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ repository_id: repository,
+ ids: names
+ },
+ format: :json
+ end
+ end
end
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 89dece97a35..aefdc4d6d4f 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Container Registry", :js do
+describe 'Container Registry', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -40,8 +40,7 @@ describe "Container Registry", :js do
it 'user removes entire container repository' do
visit_container_registry
- expect_any_instance_of(ContainerRepository)
- .to receive(:delete_tags!).and_return(true)
+ expect_any_instance_of(ContainerRepository).to receive(:delete_tags!).and_return(true)
click_on(class: 'js-remove-repo')
expect(find('.modal .modal-title')).to have_content 'Remove repository'
@@ -54,10 +53,9 @@ describe "Container Registry", :js do
find('.js-toggle-repo').click
wait_for_requests
- expect_any_instance_of(ContainerRegistry::Tag)
- .to receive(:delete).and_return(true)
+ expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete).and_return(true)
- click_on(class: 'js-delete-registry')
+ click_on(class: 'js-delete-registry-row', visible: false)
expect(find('.modal .modal-title')).to have_content 'Remove image'
find('.modal .modal-footer .btn-danger').click
end
diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js
index 31ac970378e..9c7439206ef 100644
--- a/spec/javascripts/registry/components/table_registry_spec.js
+++ b/spec/javascripts/registry/components/table_registry_spec.js
@@ -1,61 +1,159 @@
import Vue from 'vue';
import tableRegistry from '~/registry/components/table_registry.vue';
import store from '~/registry/stores';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { repoPropsData } from '../mock_data';
-const [firstImage] = repoPropsData.list;
+const [firstImage, secondImage] = repoPropsData.list;
describe('table registry', () => {
let vm;
- let Component;
+ const Component = Vue.extend(tableRegistry);
+ const bulkDeletePath = 'path';
const findDeleteBtn = () => vm.$el.querySelector('.js-delete-registry');
+ const findDeleteBtnRow = () => vm.$el.querySelector('.js-delete-registry-row');
+ const findSelectAllCheckbox = () => vm.$el.querySelector('.js-select-all-checkbox > input');
+ const findAllRowCheckboxes = () =>
+ Array.from(vm.$el.querySelectorAll('.js-select-checkbox input'));
+ const confirmationModal = (child = '') => document.querySelector(`#${vm.modalId} ${child}`);
- beforeEach(() => {
- Component = Vue.extend(tableRegistry);
- vm = new Component({
+ const createComponent = () => {
+ vm = mountComponentWithStore(Component, {
store,
- propsData: {
+ props: {
repo: repoPropsData,
},
- }).$mount();
+ });
+ };
+
+ const selectAllCheckboxes = () => vm.selectAll();
+ const deselectAllCheckboxes = () => vm.deselectAll();
+
+ beforeEach(() => {
+ createComponent();
});
afterEach(() => {
vm.$destroy();
});
- it('should render a table with the registry list', () => {
- expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
+ describe('rendering', () => {
+ it('should render a table with the registry list', () => {
+ expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
+ });
+
+ it('should render registry tag', () => {
+ const textRendered = vm.$el
+ .querySelector('.table tbody tr')
+ .textContent.trim()
+ // replace additional whitespace characters (e.g. new lines) with a single empty space
+ .replace(/\s\s+/g, ' ');
+
+ expect(textRendered).toContain(repoPropsData.list[0].tag);
+ expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
+ expect(textRendered).toContain(repoPropsData.list[0].layers);
+ expect(textRendered).toContain(repoPropsData.list[0].size);
+ });
});
- it('should render registry tag', () => {
- const textRendered = vm.$el
- .querySelector('.table tbody tr')
- .textContent.trim()
- .replace(/\s\s+/g, ' ');
+ describe('multi select', () => {
+ it('should support multiselect and selecting a row should enable delete button', done => {
+ findSelectAllCheckbox().click();
+ selectAllCheckboxes();
+
+ expect(findSelectAllCheckbox().checked).toBe(true);
+
+ Vue.nextTick(() => {
+ expect(findDeleteBtn().disabled).toBe(false);
+ done();
+ });
+ });
+
+ it('selecting all checkbox should select all rows and enable delete button', done => {
+ selectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
+
+ expect(checkedValues.length).toBe(repoPropsData.list.length);
+ done();
+ });
+ });
+
+ it('deselecting select all checkbox should deselect all rows and disable delete button', done => {
+ selectAllCheckboxes();
+ deselectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
+
+ expect(checkedValues.length).toBe(0);
+ done();
+ });
+ });
+
+ it('should delete multiple items when multiple items are selected', done => {
+ selectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0, 1]);
+ expect(findDeleteBtn().disabled).toBe(false);
+
+ findDeleteBtn().click();
+ spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
+
+ Vue.nextTick(() => {
+ const modal = confirmationModal();
+ confirmationModal('.btn-danger').click();
+
+ expect(modal).toExist();
- expect(textRendered).toContain(repoPropsData.list[0].tag);
- expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
- expect(textRendered).toContain(repoPropsData.list[0].layers);
- expect(textRendered).toContain(repoPropsData.list[0].size);
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([]);
+ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
+ path: bulkDeletePath,
+ items: [firstImage.tag, secondImage.tag],
+ });
+ done();
+ });
+ });
+ });
+ });
});
describe('delete registry', () => {
- it('should be possible to delete a registry', () => {
- expect(findDeleteBtn()).toBeDefined();
+ beforeEach(() => {
+ vm.itemsToBeDeleted = [0];
});
- it('should call deleteItem and reset itemToBeDeleted when confirming deletion', done => {
- findDeleteBtn().click();
- spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
+ it('should be possible to delete a registry', done => {
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0]);
+ expect(findDeleteBtn()).toBeDefined();
+ expect(findDeleteBtn().disabled).toBe(false);
+ expect(findDeleteBtnRow()).toBeDefined();
+ done();
+ });
+ });
+ it('should call deleteItems and reset itemsToBeDeleted when confirming deletion', done => {
Vue.nextTick(() => {
- document.querySelector(`#${vm.modalId} .btn-danger`).click();
+ expect(vm.itemsToBeDeleted).toEqual([0]);
+ expect(findDeleteBtn().disabled).toBe(false);
+ findDeleteBtn().click();
+ spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
- expect(vm.deleteItem).toHaveBeenCalledWith(firstImage);
- expect(vm.itemToBeDeleted).toBeNull();
- done();
+ Vue.nextTick(() => {
+ confirmationModal('.btn-danger').click();
+
+ expect(vm.itemsToBeDeleted).toEqual([]);
+ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
+ path: bulkDeletePath,
+ items: [firstImage.tag],
+ });
+ done();
+ });
});
});
});
@@ -65,4 +163,27 @@ describe('table registry', () => {
expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
});
});
+
+ describe('modal content', () => {
+ it('should show the singular title and image name when deleting a single image', done => {
+ findDeleteBtnRow().click();
+
+ Vue.nextTick(() => {
+ expect(vm.modalTitle).toBe('Remove image');
+ expect(vm.modalDescription).toContain(firstImage.tag);
+ done();
+ });
+ });
+
+ it('should show the plural title and image count when deleting more than one image', done => {
+ selectAllCheckboxes();
+ vm.setModalDescription();
+
+ Vue.nextTick(() => {
+ expect(vm.modalTitle).toBe('Remove images');
+ expect(vm.modalDescription).toContain('<b>2</b> images');
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/registry/mock_data.js b/spec/javascripts/registry/mock_data.js
index 22db203e77f..130ab298e89 100644
--- a/spec/javascripts/registry/mock_data.js
+++ b/spec/javascripts/registry/mock_data.js
@@ -108,6 +108,17 @@ export const repoPropsData = {
destroyPath: 'path',
canDelete: true,
},
+ {
+ tag: 'test-image',
+ revision: 'b969de599faea2b3d9b6605a8b0897261c571acaa36db1bdc7349b5775b4e0b4',
+ shortRevision: 'b969de599',
+ size: 19,
+ layers: 10,
+ location: 'location-2',
+ createdAt: 1505828744434,
+ destroyPath: 'path-2',
+ canDelete: true,
+ },
],
location: 'location',
name: 'foo',