summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-31 18:07:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-31 18:07:42 +0000
commit580622bdb3c762a8e89facd8a3946881ee480442 (patch)
tree3ac9d759da23f78f95f50684bd238a9f76839538 /spec/frontend
parentb211a4ea14d5e9ed9b0c248a4e8c5c1d85b542cb (diff)
downloadgitlab-ce-580622bdb3c762a8e89facd8a3946881ee480442.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/__mocks__/sortablejs/index.js5
-rw-r--r--spec/frontend/blob/pipeline_tour_success_mock_data.js7
-rw-r--r--spec/frontend/blob/pipeline_tour_success_modal_spec.js (renamed from spec/frontend/blob/pipeline_tour_success_spec.js)25
-rw-r--r--spec/frontend/boards/board_card_spec.js213
-rw-r--r--spec/frontend/boards/board_list_spec.js274
-rw-r--r--spec/frontend/boards/list_spec.js232
-rw-r--r--spec/frontend/registry/list/components/__snapshots__/group_empty_state_spec.js.snap61
-rw-r--r--spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap186
-rw-r--r--spec/frontend/registry/list/components/app_spec.js149
-rw-r--r--spec/frontend/registry/list/components/collapsible_container_spec.js176
-rw-r--r--spec/frontend/registry/list/components/group_empty_state_spec.js23
-rw-r--r--spec/frontend/registry/list/components/project_empty_state_spec.js27
-rw-r--r--spec/frontend/registry/list/components/table_registry_spec.js373
-rw-r--r--spec/frontend/registry/list/mock_data.js134
-rw-r--r--spec/frontend/registry/list/stores/actions_spec.js203
-rw-r--r--spec/frontend/registry/list/stores/getters_spec.js52
-rw-r--r--spec/frontend/registry/list/stores/mutations_spec.js94
17 files changed, 749 insertions, 1485 deletions
diff --git a/spec/frontend/__mocks__/sortablejs/index.js b/spec/frontend/__mocks__/sortablejs/index.js
new file mode 100644
index 00000000000..a1166d21561
--- /dev/null
+++ b/spec/frontend/__mocks__/sortablejs/index.js
@@ -0,0 +1,5 @@
+import Sortablejs from 'sortablejs';
+
+export default Sortablejs;
+export const Sortable = Sortablejs;
+export class MultiDrag {}
diff --git a/spec/frontend/blob/pipeline_tour_success_mock_data.js b/spec/frontend/blob/pipeline_tour_success_mock_data.js
new file mode 100644
index 00000000000..7819fcce85d
--- /dev/null
+++ b/spec/frontend/blob/pipeline_tour_success_mock_data.js
@@ -0,0 +1,7 @@
+const modalProps = {
+ goToPipelinesPath: 'some_pipeline_path',
+ commitCookie: 'some_cookie',
+ humanAccess: 'maintainer',
+};
+
+export default modalProps;
diff --git a/spec/frontend/blob/pipeline_tour_success_spec.js b/spec/frontend/blob/pipeline_tour_success_modal_spec.js
index f6783b31a73..613a7ce8303 100644
--- a/spec/frontend/blob/pipeline_tour_success_spec.js
+++ b/spec/frontend/blob/pipeline_tour_success_modal_spec.js
@@ -2,19 +2,20 @@ import pipelineTourSuccess from '~/blob/pipeline_tour_success_modal.vue';
import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
import { GlSprintf, GlModal } from '@gitlab/ui';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+import modalProps from './pipeline_tour_success_mock_data';
describe('PipelineTourSuccessModal', () => {
let wrapper;
let cookieSpy;
- const goToPipelinesPath = 'some_pipeline_path';
- const commitCookie = 'some_cookie';
+ let trackingSpy;
beforeEach(() => {
+ document.body.dataset.page = 'projects:blob:show';
+
+ trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
wrapper = shallowMount(pipelineTourSuccess, {
- propsData: {
- goToPipelinesPath,
- commitCookie,
- },
+ propsData: modalProps,
});
cookieSpy = jest.spyOn(Cookies, 'remove');
@@ -22,6 +23,7 @@ describe('PipelineTourSuccessModal', () => {
afterEach(() => {
wrapper.destroy();
+ unmockTracking();
});
it('has expected structure', () => {
@@ -35,6 +37,15 @@ describe('PipelineTourSuccessModal', () => {
it('calls to remove cookie', () => {
wrapper.vm.disableModalFromRenderingAgain();
- expect(cookieSpy).toHaveBeenCalledWith(commitCookie);
+ expect(cookieSpy).toHaveBeenCalledWith(modalProps.commitCookie);
+ });
+
+ describe('tracking', () => {
+ it('send event for basic view of popover', () => {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, undefined, {
+ label: 'congratulate_first_pipeline',
+ property: modalProps.humanAccess,
+ });
+ });
});
});
diff --git a/spec/frontend/boards/board_card_spec.js b/spec/frontend/boards/board_card_spec.js
new file mode 100644
index 00000000000..2524af21826
--- /dev/null
+++ b/spec/frontend/boards/board_card_spec.js
@@ -0,0 +1,213 @@
+/* global List */
+/* global ListAssignee */
+/* global ListLabel */
+
+import { shallowMount } from '@vue/test-utils';
+
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import waitForPromises from 'helpers/wait_for_promises';
+
+import eventHub from '~/boards/eventhub';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
+import '~/boards/models/list';
+import store from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
+import boardCard from '~/boards/components/board_card.vue';
+import issueCardInner from '~/boards/components/issue_card_inner.vue';
+import userAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import { listObj, boardsMockInterceptor, setMockEndpoints } from './mock_data';
+
+describe('Board card', () => {
+ let wrapper;
+ let mock;
+ let list;
+
+ const findIssueCardInner = () => wrapper.find(issueCardInner);
+ const findUserAvatarLink = () => wrapper.find(userAvatarLink);
+
+ // this particular mount component needs to be used after the root beforeEach because it depends on list being initialized
+ const mountComponent = propsData => {
+ wrapper = shallowMount(boardCard, {
+ stubs: {
+ issueCardInner,
+ },
+ store,
+ propsData: {
+ list,
+ issue: list.issues[0],
+ issueLinkBase: '/',
+ disabled: false,
+ index: 0,
+ rootPath: '/',
+ ...propsData,
+ },
+ });
+ };
+
+ const setupData = () => {
+ list = new List(listObj);
+ boardsStore.create();
+ boardsStore.detail.issue = {};
+ const label1 = new ListLabel({
+ id: 3,
+ title: 'testing 123',
+ color: '#000cff',
+ text_color: 'white',
+ description: 'test',
+ });
+ return waitForPromises().then(() => {
+ list.issues[0].labels.push(label1);
+ });
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onAny().reply(boardsMockInterceptor);
+ setMockEndpoints();
+ return setupData();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ list = null;
+ mock.restore();
+ });
+
+ it('when details issue is empty does not show the element', () => {
+ mountComponent();
+ expect(wrapper.classes()).not.toContain('is-active');
+ });
+
+ it('when detailIssue is equal to card issue shows the element', () => {
+ [boardsStore.detail.issue] = list.issues;
+ mountComponent();
+
+ expect(wrapper.classes()).toContain('is-active');
+ });
+
+ it('when multiSelect does not contain issue removes multi select class', () => {
+ mountComponent();
+ expect(wrapper.classes()).not.toContain('multi-select');
+ });
+
+ it('when multiSelect contain issue add multi select class', () => {
+ boardsStore.multiSelect.list = [list.issues[0]];
+ mountComponent();
+
+ expect(wrapper.classes()).toContain('multi-select');
+ });
+
+ it('adds user-can-drag class if not disabled', () => {
+ mountComponent();
+ expect(wrapper.classes()).toContain('user-can-drag');
+ });
+
+ it('does not add user-can-drag class disabled', () => {
+ mountComponent({ disabled: true });
+
+ expect(wrapper.classes()).not.toContain('user-can-drag');
+ });
+
+ it('does not add disabled class', () => {
+ mountComponent();
+ expect(wrapper.classes()).not.toContain('is-disabled');
+ });
+
+ it('adds disabled class is disabled is true', () => {
+ mountComponent({ disabled: true });
+
+ expect(wrapper.classes()).toContain('is-disabled');
+ });
+
+ describe('mouse events', () => {
+ it('sets showDetail to true on mousedown', () => {
+ mountComponent();
+ wrapper.trigger('mousedown');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.showDetail).toBe(true);
+ });
+ });
+
+ it('sets showDetail to false on mousemove', () => {
+ mountComponent();
+ wrapper.trigger('mousedown');
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.vm.showDetail).toBe(true);
+ wrapper.trigger('mousemove');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.vm.showDetail).toBe(false);
+ });
+ });
+
+ it('does not set detail issue if showDetail is false', () => {
+ mountComponent();
+ expect(boardsStore.detail.issue).toEqual({});
+ });
+
+ it('does not set detail issue if link is clicked', () => {
+ mountComponent();
+ findIssueCardInner()
+ .find('a')
+ .trigger('mouseup');
+
+ expect(boardsStore.detail.issue).toEqual({});
+ });
+
+ it('does not set detail issue if img is clicked', () => {
+ mountComponent({
+ issue: {
+ ...list.issues[0],
+ assignees: [
+ new ListAssignee({
+ id: 1,
+ name: 'testing 123',
+ username: 'test',
+ avatar: 'test_image',
+ }),
+ ],
+ },
+ });
+
+ findUserAvatarLink().trigger('mouseup');
+
+ expect(boardsStore.detail.issue).toEqual({});
+ });
+
+ it('does not set detail issue if showDetail is false after mouseup', () => {
+ mountComponent();
+ wrapper.trigger('mouseup');
+
+ expect(boardsStore.detail.issue).toEqual({});
+ });
+
+ it('sets detail issue to card issue on mouse up', () => {
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+
+ mountComponent();
+
+ wrapper.trigger('mousedown');
+ wrapper.trigger('mouseup');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', wrapper.vm.issue, undefined);
+ expect(boardsStore.detail.list).toEqual(wrapper.vm.list);
+ });
+
+ it('resets detail issue to empty if already set', () => {
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ [boardsStore.detail.issue] = list.issues;
+ mountComponent();
+
+ wrapper.trigger('mousedown');
+ wrapper.trigger('mouseup');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue', undefined);
+ });
+ });
+});
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
new file mode 100644
index 00000000000..882310030f8
--- /dev/null
+++ b/spec/frontend/boards/board_list_spec.js
@@ -0,0 +1,274 @@
+/* global List */
+/* global ListIssue */
+
+import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import eventHub from '~/boards/eventhub';
+import waitForPromises from '../helpers/wait_for_promises';
+import BoardList from '~/boards/components/board_list.vue';
+import '~/boards/models/issue';
+import '~/boards/models/list';
+import { listObj, boardsMockInterceptor } from './mock_data';
+import store from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
+
+const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listProps = {} }) => {
+ const el = document.createElement('div');
+
+ document.body.appendChild(el);
+ const mock = new MockAdapter(axios);
+ mock.onAny().reply(boardsMockInterceptor);
+ boardsStore.create();
+
+ const BoardListComp = Vue.extend(BoardList);
+ const list = new List({ ...listObj, ...listProps });
+ const issue = new ListIssue({
+ title: 'Testing',
+ id: 1,
+ iid: 1,
+ confidential: false,
+ labels: [],
+ assignees: [],
+ ...listIssueProps,
+ });
+ if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesSize')) {
+ list.issuesSize = 1;
+ }
+ list.issues.push(issue);
+
+ const component = new BoardListComp({
+ el,
+ store,
+ propsData: {
+ disabled: false,
+ list,
+ issues: list.issues,
+ loading: false,
+ issueLinkBase: '/issues',
+ rootPath: '/',
+ ...componentProps,
+ },
+ }).$mount();
+
+ Vue.nextTick(() => {
+ done();
+ });
+
+ return { component, mock };
+};
+
+describe('Board list component', () => {
+ let mock;
+ let component;
+ let getIssues;
+ function generateIssues(compWrapper) {
+ for (let i = 1; i < 20; i += 1) {
+ const issue = Object.assign({}, compWrapper.list.issues[0]);
+ issue.id += i;
+ compWrapper.list.issues.push(issue);
+ }
+ }
+
+ describe('When Expanded', () => {
+ beforeEach(done => {
+ getIssues = jest.spyOn(List.prototype, 'getIssues').mockReturnValue(new Promise(() => {}));
+ ({ mock, component } = createComponent({ done }));
+ });
+
+ afterEach(() => {
+ mock.restore();
+ component.$destroy();
+ });
+
+ it('loads first page of issues', () => {
+ return waitForPromises().then(() => {
+ expect(getIssues).toHaveBeenCalled();
+ });
+ });
+
+ it('renders component', () => {
+ expect(component.$el.classList.contains('board-list-component')).toBe(true);
+ });
+
+ it('renders loading icon', () => {
+ component.loading = true;
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.board-list-loading')).not.toBeNull();
+ });
+ });
+
+ it('renders issues', () => {
+ expect(component.$el.querySelectorAll('.board-card').length).toBe(1);
+ });
+
+ it('sets data attribute with issue id', () => {
+ expect(component.$el.querySelector('.board-card').getAttribute('data-issue-id')).toBe('1');
+ });
+
+ it('shows new issue form', () => {
+ component.toggleForm();
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull();
+
+ expect(component.$el.querySelector('.is-smaller')).not.toBeNull();
+ });
+ });
+
+ it('shows new issue form after eventhub event', () => {
+ eventHub.$emit(`hide-issue-form-${component.list.id}`);
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull();
+
+ expect(component.$el.querySelector('.is-smaller')).not.toBeNull();
+ });
+ });
+
+ it('does not show new issue form for closed list', () => {
+ component.list.type = 'closed';
+ component.toggleForm();
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.board-new-issue-form')).toBeNull();
+ });
+ });
+
+ it('shows count list item', () => {
+ component.showCount = true;
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.board-list-count')).not.toBeNull();
+
+ expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe(
+ 'Showing all issues',
+ );
+ });
+ });
+
+ it('sets data attribute with invalid id', () => {
+ component.showCount = true;
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.board-list-count').getAttribute('data-issue-id')).toBe(
+ '-1',
+ );
+ });
+ });
+
+ it('shows how many more issues to load', () => {
+ component.showCount = true;
+ component.list.issuesSize = 20;
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe(
+ 'Showing 1 of 20 issues',
+ );
+ });
+ });
+
+ it('loads more issues after scrolling', () => {
+ jest.spyOn(component.list, 'nextPage').mockImplementation(() => {});
+ generateIssues(component);
+ component.$refs.list.dispatchEvent(new Event('scroll'));
+
+ return waitForPromises().then(() => {
+ expect(component.list.nextPage).toHaveBeenCalled();
+ });
+ });
+
+ it('does not load issues if already loading', () => {
+ component.list.nextPage = jest
+ .spyOn(component.list, 'nextPage')
+ .mockReturnValue(new Promise(() => {}));
+
+ component.onScroll();
+ component.onScroll();
+
+ return waitForPromises().then(() => {
+ expect(component.list.nextPage).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ it('shows loading more spinner', () => {
+ component.showCount = true;
+ component.list.loadingMore = true;
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull();
+ });
+ });
+ });
+
+ describe('When Collapsed', () => {
+ beforeEach(done => {
+ getIssues = jest.spyOn(List.prototype, 'getIssues').mockReturnValue(new Promise(() => {}));
+ ({ mock, component } = createComponent({
+ done,
+ listProps: { type: 'closed', collapsed: true, issuesSize: 50 },
+ }));
+ generateIssues(component);
+ component.scrollHeight = jest.spyOn(component, 'scrollHeight').mockReturnValue(0);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ component.$destroy();
+ });
+
+ it('does not load all issues', () => {
+ return waitForPromises().then(() => {
+ // Initial getIssues from list constructor
+ expect(getIssues).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+
+ describe('max issue count warning', () => {
+ beforeEach(done => {
+ ({ mock, component } = createComponent({
+ done,
+ listProps: { type: 'closed', collapsed: true, issuesSize: 50 },
+ }));
+ });
+
+ afterEach(() => {
+ mock.restore();
+ component.$destroy();
+ });
+
+ describe('when issue count exceeds max issue count', () => {
+ it('sets background to bg-danger-100', () => {
+ component.list.issuesSize = 4;
+ component.list.maxIssueCount = 3;
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.bg-danger-100')).not.toBeNull();
+ });
+ });
+ });
+
+ describe('when list issue count does NOT exceed list max issue count', () => {
+ it('does not sets background to bg-danger-100', () => {
+ component.list.issuesSize = 2;
+ component.list.maxIssueCount = 3;
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.bg-danger-100')).toBeNull();
+ });
+ });
+ });
+
+ describe('when list max issue count is 0', () => {
+ it('does not sets background to bg-danger-100', () => {
+ component.list.maxIssueCount = 0;
+
+ return Vue.nextTick().then(() => {
+ expect(component.$el.querySelector('.bg-danger-100')).toBeNull();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/list_spec.js b/spec/frontend/boards/list_spec.js
new file mode 100644
index 00000000000..c0dd5afe498
--- /dev/null
+++ b/spec/frontend/boards/list_spec.js
@@ -0,0 +1,232 @@
+/* global List */
+/* global ListAssignee */
+/* global ListIssue */
+/* global ListLabel */
+
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
+import '~/boards/models/issue';
+import '~/boards/models/list';
+import { ListType } from '~/boards/constants';
+import boardsStore from '~/boards/stores/boards_store';
+import waitForPromises from 'helpers/wait_for_promises';
+import { listObj, listObjDuplicate, boardsMockInterceptor } from './mock_data';
+
+describe('List model', () => {
+ let list;
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onAny().reply(boardsMockInterceptor);
+ boardsStore.create();
+ boardsStore.setEndpoints({
+ listsEndpoint: '/test/-/boards/1/lists',
+ });
+
+ list = new List(listObj);
+ return waitForPromises();
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('list type', () => {
+ const notExpandableList = ['blank'];
+
+ const table = Object.keys(ListType).map(k => {
+ const value = ListType[k];
+ return [value, !notExpandableList.includes(value)];
+ });
+ it.each(table)(`when list_type is %s boards isExpandable is %p`, (type, result) => {
+ expect(new List({ id: 1, list_type: type }).isExpandable).toBe(result);
+ });
+ });
+
+ it('gets issues when created', () => {
+ expect(list.issues.length).toBe(1);
+ });
+
+ it('saves list and returns ID', () => {
+ list = new List({
+ title: 'test',
+ label: {
+ id: 1,
+ title: 'test',
+ color: 'red',
+ text_color: 'white',
+ },
+ });
+ return list.save().then(() => {
+ expect(list.id).toBe(listObj.id);
+ expect(list.type).toBe('label');
+ expect(list.position).toBe(0);
+ expect(list.label.color).toBe('red');
+ expect(list.label.textColor).toBe('white');
+ });
+ });
+
+ it('destroys the list', () => {
+ boardsStore.addList(listObj);
+ list = boardsStore.findList('id', listObj.id);
+
+ expect(boardsStore.state.lists.length).toBe(1);
+ list.destroy();
+
+ return waitForPromises().then(() => {
+ expect(boardsStore.state.lists.length).toBe(0);
+ });
+ });
+
+ it('gets issue from list', () => {
+ const issue = list.findIssue(1);
+
+ expect(issue).toBeDefined();
+ });
+
+ it('removes issue', () => {
+ const issue = list.findIssue(1);
+
+ expect(list.issues.length).toBe(1);
+ list.removeIssue(issue);
+
+ expect(list.issues.length).toBe(0);
+ });
+
+ it('sends service request to update issue label', () => {
+ const listDup = new List(listObjDuplicate);
+ const issue = new ListIssue({
+ title: 'Testing',
+ id: 1,
+ iid: 1,
+ confidential: false,
+ labels: [list.label, listDup.label],
+ assignees: [],
+ });
+
+ list.issues.push(issue);
+ listDup.issues.push(issue);
+
+ jest.spyOn(boardsStore, 'moveIssue');
+
+ listDup.updateIssueLabel(issue, list);
+
+ expect(boardsStore.moveIssue).toHaveBeenCalledWith(
+ issue.id,
+ list.id,
+ listDup.id,
+ undefined,
+ undefined,
+ );
+ });
+
+ describe('page number', () => {
+ beforeEach(() => {
+ jest.spyOn(list, 'getIssues').mockImplementation(() => {});
+ list.issues = [];
+ });
+
+ it('increase page number if current issue count is more than the page size', () => {
+ for (let i = 0; i < 30; i += 1) {
+ list.issues.push(
+ new ListIssue({
+ title: 'Testing',
+ id: i,
+ iid: i,
+ confidential: false,
+ labels: [list.label],
+ assignees: [],
+ }),
+ );
+ }
+ list.issuesSize = 50;
+
+ expect(list.issues.length).toBe(30);
+
+ list.nextPage();
+
+ expect(list.page).toBe(2);
+ expect(list.getIssues).toHaveBeenCalled();
+ });
+
+ it('does not increase page number if issue count is less than the page size', () => {
+ list.issues.push(
+ new ListIssue({
+ title: 'Testing',
+ id: 1,
+ confidential: false,
+ labels: [list.label],
+ assignees: [],
+ }),
+ );
+ list.issuesSize = 2;
+
+ list.nextPage();
+
+ expect(list.page).toBe(1);
+ expect(list.getIssues).toHaveBeenCalled();
+ });
+ });
+
+ describe('newIssue', () => {
+ beforeEach(() => {
+ jest.spyOn(boardsStore, 'newIssue').mockReturnValue(
+ Promise.resolve({
+ data: {
+ id: 42,
+ subscribed: false,
+ assignable_labels_endpoint: '/issue/42/labels',
+ toggle_subscription_endpoint: '/issue/42/subscriptions',
+ issue_sidebar_endpoint: '/issue/42/sidebar_info',
+ },
+ }),
+ );
+ list.issues = [];
+ });
+
+ it('adds new issue to top of list', done => {
+ const user = new ListAssignee({
+ id: 1,
+ name: 'testing 123',
+ username: 'test',
+ avatar: 'test_image',
+ });
+
+ list.issues.push(
+ new ListIssue({
+ title: 'Testing',
+ id: 1,
+ confidential: false,
+ labels: [new ListLabel(list.label)],
+ assignees: [],
+ }),
+ );
+ const dummyIssue = new ListIssue({
+ title: 'new issue',
+ id: 2,
+ confidential: false,
+ labels: [new ListLabel(list.label)],
+ assignees: [user],
+ subscribed: false,
+ });
+
+ list
+ .newIssue(dummyIssue)
+ .then(() => {
+ expect(list.issues.length).toBe(2);
+ expect(list.issues[0]).toBe(dummyIssue);
+ expect(list.issues[0].subscribed).toBe(false);
+ expect(list.issues[0].assignableLabelsEndpoint).toBe('/issue/42/labels');
+ expect(list.issues[0].toggleSubscriptionEndpoint).toBe('/issue/42/subscriptions');
+ expect(list.issues[0].sidebarInfoEndpoint).toBe('/issue/42/sidebar_info');
+ expect(list.issues[0].labels).toBe(dummyIssue.labels);
+ expect(list.issues[0].assignees).toBe(dummyIssue.assignees);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/frontend/registry/list/components/__snapshots__/group_empty_state_spec.js.snap b/spec/frontend/registry/list/components/__snapshots__/group_empty_state_spec.js.snap
deleted file mode 100644
index 3f13b7d4d76..00000000000
--- a/spec/frontend/registry/list/components/__snapshots__/group_empty_state_spec.js.snap
+++ /dev/null
@@ -1,61 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Registry Group Empty state to match the default snapshot 1`] = `
-<div
- class="row container-message empty-state"
->
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt="There are no container images available in this group"
- class=""
- src="imageUrl"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content"
- >
- <h4
- class="center"
- style=""
- >
- There are no container images available in this group
- </h4>
-
- <p
- class="center"
- style=""
- >
- <p
- class="js-no-container-images-text"
- >
- With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here.
- <a
- href="help"
- target="_blank"
- >
- More Information
- </a>
- </p>
- </p>
-
- <div
- class="text-center"
- >
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
-</div>
-`;
diff --git a/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap b/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap
deleted file mode 100644
index c072950f3e2..00000000000
--- a/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap
+++ /dev/null
@@ -1,186 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Registry Project Empty state to match the default snapshot 1`] = `
-<div
- class="row container-message empty-state"
->
- <div
- class="col-12"
- >
- <div
- class="svg-250 svg-content"
- >
- <img
- alt="There are no container images stored for this project"
- class=""
- src="imageUrl"
- />
- </div>
- </div>
-
- <div
- class="col-12"
- >
- <div
- class="text-content"
- >
- <h4
- class="center"
- style=""
- >
- There are no container images stored for this project
- </h4>
-
- <p
- class="center"
- style=""
- >
- <p
- class="js-no-container-images-text"
- >
- With the Container Registry, every project can have its own space to store its Docker images.
- <a
- href="help"
- target="_blank"
- >
- More Information
- </a>
- </p>
-
- <h5>
- Quick Start
- </h5>
-
- <p
- class="js-not-logged-in-to-registry-text"
- >
- If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have
- <a
- href="help_link"
- target="_blank"
- >
- Two-Factor Authentication
- </a>
- enabled, use a
- <a
- href="personal_token"
- target="_blank"
- >
- Personal Access Token
- </a>
- instead of a password.
- </p>
-
- <div
- class="input-group append-bottom-10"
- >
- <input
- class="form-control monospace"
- readonly="readonly"
- type="text"
- />
-
- <span
- class="input-group-append"
- >
- <button
- class="btn input-group-text btn-secondary btn-md btn-default"
- data-clipboard-text="docker login host"
- title="Copy login command"
- type="button"
- >
- <!---->
-
- <svg
- class="gl-icon s16"
- >
- <use
- href="#copy-to-clipboard"
- />
- </svg>
- </button>
- </span>
- </div>
-
- <p />
-
- <p>
-
- You can add an image to this registry with the following commands:
-
- </p>
-
- <div
- class="input-group append-bottom-10"
- >
- <input
- class="form-control monospace"
- readonly="readonly"
- type="text"
- />
-
- <span
- class="input-group-append"
- >
- <button
- class="btn input-group-text btn-secondary btn-md btn-default"
- data-clipboard-text="docker build -t url ."
- title="Copy build command"
- type="button"
- >
- <!---->
-
- <svg
- class="gl-icon s16"
- >
- <use
- href="#copy-to-clipboard"
- />
- </svg>
- </button>
- </span>
- </div>
-
- <div
- class="input-group"
- >
- <input
- class="form-control monospace"
- readonly="readonly"
- type="text"
- />
-
- <span
- class="input-group-append"
- >
- <button
- class="btn input-group-text btn-secondary btn-md btn-default"
- data-clipboard-text="docker push url"
- title="Copy push command"
- type="button"
- >
- <!---->
-
- <svg
- class="gl-icon s16"
- >
- <use
- href="#copy-to-clipboard"
- />
- </svg>
- </button>
- </span>
- </div>
- </p>
-
- <div
- class="text-center"
- >
- <!---->
-
- <!---->
- </div>
- </div>
- </div>
-</div>
-`;
diff --git a/spec/frontend/registry/list/components/app_spec.js b/spec/frontend/registry/list/components/app_spec.js
deleted file mode 100644
index c2c220b2cd2..00000000000
--- a/spec/frontend/registry/list/components/app_spec.js
+++ /dev/null
@@ -1,149 +0,0 @@
-import { mount } from '@vue/test-utils';
-import { TEST_HOST } from 'helpers/test_constants';
-import registry from '~/registry/list/components/app.vue';
-import { reposServerResponse, parsedReposServerResponse } from '../mock_data';
-
-describe('Registry List', () => {
- let wrapper;
-
- const findCollapsibleContainer = () => wrapper.findAll({ name: 'CollapsibeContainerRegisty' });
- const findProjectEmptyState = () => wrapper.find({ name: 'ProjectEmptyState' });
- const findGroupEmptyState = () => wrapper.find({ name: 'GroupEmptyState' });
- const findSpinner = () => wrapper.find('.gl-spinner');
- const findCharacterErrorText = () => wrapper.find('.js-character-error-text');
-
- const propsData = {
- endpoint: `${TEST_HOST}/foo`,
- helpPagePath: 'foo',
- noContainersImage: 'foo',
- containersErrorImage: 'foo',
- repositoryUrl: 'foo',
- registryHostUrlWithPort: 'foo',
- personalAccessTokensHelpLink: 'foo',
- twoFactorAuthHelpLink: 'foo',
- };
-
- const setMainEndpoint = jest.fn();
- const fetchRepos = jest.fn();
- const setIsDeleteDisabled = jest.fn();
-
- const methods = {
- setMainEndpoint,
- fetchRepos,
- setIsDeleteDisabled,
- };
-
- beforeEach(() => {
- wrapper = mount(registry, {
- propsData,
- computed: {
- repos() {
- return parsedReposServerResponse;
- },
- },
- methods,
- });
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('with data', () => {
- it('should render a list of CollapsibeContainerRegisty', () => {
- const containers = findCollapsibleContainer();
- expect(wrapper.vm.repos.length).toEqual(reposServerResponse.length);
- expect(containers.length).toEqual(reposServerResponse.length);
- });
- });
-
- describe('without data', () => {
- beforeEach(() => {
- wrapper = mount(registry, {
- propsData,
- computed: {
- repos() {
- return [];
- },
- },
- methods,
- });
- });
-
- it('should render project empty message', () => {
- const projectEmptyState = findProjectEmptyState();
- expect(projectEmptyState.exists()).toBe(true);
- });
- });
-
- describe('while loading data', () => {
- beforeEach(() => {
- wrapper = mount(registry, {
- propsData,
- computed: {
- repos() {
- return [];
- },
- isLoading() {
- return true;
- },
- },
- methods,
- });
- });
-
- it('should render a loading spinner', () => {
- const spinner = findSpinner();
- expect(spinner.exists()).toBe(true);
- });
- });
-
- describe('invalid characters in path', () => {
- beforeEach(() => {
- wrapper = mount(registry, {
- propsData: {
- ...propsData,
- characterError: true,
- },
- computed: {
- repos() {
- return [];
- },
- },
- methods,
- });
- });
-
- it('should render invalid characters error message', () => {
- const characterErrorText = findCharacterErrorText();
- expect(characterErrorText.text()).toEqual(
- 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More Information',
- );
- });
- });
-
- describe('with groupId set', () => {
- const isGroupPage = true;
-
- beforeEach(() => {
- wrapper = mount(registry, {
- propsData: {
- ...propsData,
- endpoint: '',
- isGroupPage,
- },
- methods,
- });
- });
-
- it('call the right vuex setters', () => {
- expect(methods.setMainEndpoint).toHaveBeenLastCalledWith('');
- expect(methods.setIsDeleteDisabled).toHaveBeenLastCalledWith(true);
- });
-
- it('should render groups empty message', () => {
- const groupEmptyState = findGroupEmptyState(wrapper);
- expect(groupEmptyState.exists()).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/registry/list/components/collapsible_container_spec.js b/spec/frontend/registry/list/components/collapsible_container_spec.js
deleted file mode 100644
index f969f0ba9ba..00000000000
--- a/spec/frontend/registry/list/components/collapsible_container_spec.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
-import createFlash from '~/flash';
-import Tracking from '~/tracking';
-import collapsibleComponent from '~/registry/list/components/collapsible_container.vue';
-import * as getters from '~/registry/list/stores/getters';
-import { repoPropsData } from '../mock_data';
-
-jest.mock('~/flash.js');
-
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
-
-describe('collapsible registry container', () => {
- let wrapper;
- let store;
-
- const findDeleteBtn = () => wrapper.find('.js-remove-repo');
- const findContainerImageTags = () => wrapper.find('.container-image-tags');
- const findToggleRepos = () => wrapper.findAll('.js-toggle-repo');
- const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' });
-
- const mountWithStore = config =>
- mount(collapsibleComponent, {
- ...config,
- store,
- localVue,
- });
-
- beforeEach(() => {
- createFlash.mockClear();
- store = new Vuex.Store({
- state: {
- isDeleteDisabled: false,
- },
- getters,
- });
-
- wrapper = mountWithStore({
- propsData: {
- repo: repoPropsData,
- },
- });
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('toggle', () => {
- beforeEach(() => {
- const fetchList = jest.fn();
- wrapper.setMethods({ fetchList });
- return wrapper.vm.$nextTick();
- });
-
- const expectIsClosed = () => {
- const container = findContainerImageTags();
- expect(container.exists()).toBe(false);
- expect(wrapper.vm.iconName).toEqual('angle-right');
- };
-
- it('should be closed by default', () => {
- expectIsClosed();
- });
-
- it('should be open when user clicks on closed repo', () => {
- const toggleRepos = findToggleRepos();
- toggleRepos.at(0).trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- const container = findContainerImageTags();
- expect(container.exists()).toBe(true);
- expect(wrapper.vm.fetchList).toHaveBeenCalled();
- });
- });
-
- it('should be closed when the user clicks on an opened repo', () => {
- const toggleRepos = findToggleRepos();
- toggleRepos.at(0).trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- toggleRepos.at(0).trigger('click');
- wrapper.vm.$nextTick(() => {
- expectIsClosed();
- });
- });
- });
- });
-
- describe('delete repo', () => {
- beforeEach(() => {
- const deleteItem = jest.fn().mockResolvedValue();
- const fetchRepos = jest.fn().mockResolvedValue();
- wrapper.setMethods({ deleteItem, fetchRepos });
- });
-
- it('should be possible to delete a repo', () => {
- const deleteBtn = findDeleteBtn();
- expect(deleteBtn.exists()).toBe(true);
- });
-
- it('should call deleteItem when confirming deletion', () => {
- wrapper.vm.handleDeleteRepository();
- expect(wrapper.vm.deleteItem).toHaveBeenCalledWith(wrapper.vm.repo);
- });
-
- it('should show a flash with a success notice', () =>
- wrapper.vm.handleDeleteRepository().then(() => {
- expect(wrapper.vm.deleteImageConfirmationMessage).toContain(wrapper.vm.repo.name);
- expect(createFlash).toHaveBeenCalledWith(
- wrapper.vm.deleteImageConfirmationMessage,
- 'notice',
- );
- }));
-
- it('should show an error when there is API error', () => {
- const deleteItem = jest.fn().mockRejectedValue('error');
- wrapper.setMethods({ deleteItem });
- return wrapper.vm.handleDeleteRepository().then(() => {
- expect(createFlash).toHaveBeenCalled();
- });
- });
- });
-
- describe('disabled delete', () => {
- beforeEach(() => {
- store = new Vuex.Store({
- state: {
- isDeleteDisabled: true,
- },
- getters,
- });
- wrapper = mountWithStore({
- propsData: {
- repo: repoPropsData,
- },
- });
- });
-
- it('should not render delete button', () => {
- const deleteBtn = findDeleteBtn();
- expect(deleteBtn.exists()).toBe(false);
- });
- });
-
- describe('tracking', () => {
- const testTrackingCall = action => {
- expect(Tracking.event).toHaveBeenCalledWith(undefined, action, {
- label: 'registry_repository_delete',
- });
- };
-
- beforeEach(() => {
- jest.spyOn(Tracking, 'event');
- wrapper.vm.deleteItem = jest.fn().mockResolvedValue();
- wrapper.vm.fetchRepos = jest.fn();
- });
-
- it('send an event when delete button is clicked', () => {
- const deleteBtn = findDeleteBtn();
- deleteBtn.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');
- });
- });
-});
diff --git a/spec/frontend/registry/list/components/group_empty_state_spec.js b/spec/frontend/registry/list/components/group_empty_state_spec.js
deleted file mode 100644
index 7541c3d459c..00000000000
--- a/spec/frontend/registry/list/components/group_empty_state_spec.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { mount } from '@vue/test-utils';
-import groupEmptyState from '~/registry/list/components/group_empty_state.vue';
-
-describe('Registry Group Empty state', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = mount(groupEmptyState, {
- propsData: {
- noContainersImage: 'imageUrl',
- helpPagePath: 'help',
- },
- });
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('to match the default snapshot', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-});
diff --git a/spec/frontend/registry/list/components/project_empty_state_spec.js b/spec/frontend/registry/list/components/project_empty_state_spec.js
deleted file mode 100644
index d29b9e47233..00000000000
--- a/spec/frontend/registry/list/components/project_empty_state_spec.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { mount } from '@vue/test-utils';
-import projectEmptyState from '~/registry/list/components/project_empty_state.vue';
-
-describe('Registry Project Empty state', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = mount(projectEmptyState, {
- propsData: {
- noContainersImage: 'imageUrl',
- helpPagePath: 'help',
- repositoryUrl: 'url',
- twoFactorAuthHelpLink: 'help_link',
- personalAccessTokensHelpLink: 'personal_token',
- registryHostUrlWithPort: 'host',
- },
- });
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('to match the default snapshot', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-});
diff --git a/spec/frontend/registry/list/components/table_registry_spec.js b/spec/frontend/registry/list/components/table_registry_spec.js
deleted file mode 100644
index b13797929dd..00000000000
--- a/spec/frontend/registry/list/components/table_registry_spec.js
+++ /dev/null
@@ -1,373 +0,0 @@
-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');
- });
- });
- });
-});
diff --git a/spec/frontend/registry/list/mock_data.js b/spec/frontend/registry/list/mock_data.js
deleted file mode 100644
index 130ab298e89..00000000000
--- a/spec/frontend/registry/list/mock_data.js
+++ /dev/null
@@ -1,134 +0,0 @@
-export const defaultState = {
- isLoading: false,
- endpoint: '',
- repos: [],
-};
-
-export const reposServerResponse = [
- {
- destroy_path: 'path',
- id: '123',
- location: 'location',
- path: 'foo',
- tags_path: 'tags_path',
- },
- {
- destroy_path: 'path_',
- id: '456',
- location: 'location_',
- path: 'bar',
- tags_path: 'tags_path_',
- },
-];
-
-export const registryServerResponse = [
- {
- name: 'centos7',
- short_revision: 'b118ab5b0',
- revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
- total_size: 679,
- layers: 19,
- location: 'location',
- created_at: 1505828744434,
- destroy_path: 'path_',
- },
- {
- name: 'centos6',
- short_revision: 'b118ab5b0',
- revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
- total_size: 679,
- layers: 19,
- location: 'location',
- created_at: 1505828744434,
- },
-];
-
-export const parsedReposServerResponse = [
- {
- canDelete: true,
- destroyPath: reposServerResponse[0].destroy_path,
- id: reposServerResponse[0].id,
- isLoading: false,
- list: [],
- location: reposServerResponse[0].location,
- name: reposServerResponse[0].path,
- tagsPath: reposServerResponse[0].tags_path,
- },
- {
- canDelete: true,
- destroyPath: reposServerResponse[1].destroy_path,
- id: reposServerResponse[1].id,
- isLoading: false,
- list: [],
- location: reposServerResponse[1].location,
- name: reposServerResponse[1].path,
- tagsPath: reposServerResponse[1].tags_path,
- },
-];
-
-export const parsedRegistryServerResponse = [
- {
- tag: registryServerResponse[0].name,
- revision: registryServerResponse[0].revision,
- shortRevision: registryServerResponse[0].short_revision,
- size: registryServerResponse[0].total_size,
- layers: registryServerResponse[0].layers,
- location: registryServerResponse[0].location,
- createdAt: registryServerResponse[0].created_at,
- destroyPath: registryServerResponse[0].destroy_path,
- canDelete: true,
- },
- {
- tag: registryServerResponse[1].name,
- revision: registryServerResponse[1].revision,
- shortRevision: registryServerResponse[1].short_revision,
- size: registryServerResponse[1].total_size,
- layers: registryServerResponse[1].layers,
- location: registryServerResponse[1].location,
- createdAt: registryServerResponse[1].created_at,
- destroyPath: registryServerResponse[1].destroy_path,
- canDelete: false,
- },
-];
-
-export const repoPropsData = {
- canDelete: true,
- destroyPath: 'path',
- id: '123',
- isLoading: false,
- list: [
- {
- tag: 'centos6',
- revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
- shortRevision: 'b118ab5b0',
- size: 19,
- layers: 10,
- location: 'location',
- createdAt: 1505828744434,
- 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',
- tagsPath: 'path',
- pagination: {
- perPage: 5,
- page: 1,
- total: 13,
- totalPages: 1,
- nextPage: null,
- previousPage: null,
- },
-};
diff --git a/spec/frontend/registry/list/stores/actions_spec.js b/spec/frontend/registry/list/stores/actions_spec.js
deleted file mode 100644
index 2fc363e9a4f..00000000000
--- a/spec/frontend/registry/list/stores/actions_spec.js
+++ /dev/null
@@ -1,203 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import { TEST_HOST } from 'helpers/test_constants';
-import testAction from 'helpers/vuex_action_helper';
-import axios from '~/lib/utils/axios_utils';
-import * as actions from '~/registry/list/stores/actions';
-import * as types from '~/registry/list/stores/mutation_types';
-import createFlash from '~/flash';
-
-import {
- reposServerResponse,
- registryServerResponse,
- parsedReposServerResponse,
-} from '../mock_data';
-
-jest.mock('~/flash.js');
-
-describe('Actions Registry Store', () => {
- let mock;
- let state;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- state = {
- endpoint: `${TEST_HOST}/endpoint.json`,
- };
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('fetchRepos', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, reposServerResponse, {});
- });
-
- it('should set received repos', done => {
- testAction(
- actions.fetchRepos,
- null,
- state,
- [
- { type: types.TOGGLE_MAIN_LOADING },
- { type: types.TOGGLE_MAIN_LOADING },
- { type: types.SET_REPOS_LIST, payload: reposServerResponse },
- ],
- [],
- done,
- );
- });
-
- it('should create flash on API error', done => {
- testAction(
- actions.fetchRepos,
- null,
- {
- endpoint: null,
- },
- [{ type: types.TOGGLE_MAIN_LOADING }, { type: types.TOGGLE_MAIN_LOADING }],
- [],
- () => {
- expect(createFlash).toHaveBeenCalled();
- done();
- },
- );
- });
- });
-
- describe('fetchList', () => {
- let repo;
- beforeEach(() => {
- state.repos = parsedReposServerResponse;
- [, repo] = state.repos;
- });
-
- it('should set received list', done => {
- mock.onGet(repo.tagsPath).replyOnce(200, registryServerResponse, {});
- testAction(
- actions.fetchList,
- { repo },
- state,
- [
- { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
- { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
- {
- type: types.SET_REGISTRY_LIST,
- payload: {
- repo,
- resp: registryServerResponse,
- headers: expect.anything(),
- },
- },
- ],
- [],
- done,
- );
- });
-
- it('should create flash on API error', done => {
- mock.onGet(repo.tagsPath).replyOnce(400);
- const updatedRepo = {
- ...repo,
- tagsPath: null,
- };
- testAction(
- actions.fetchList,
- {
- repo: updatedRepo,
- },
- state,
- [
- { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: updatedRepo },
- { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: updatedRepo },
- ],
- [],
- () => {
- expect(createFlash).toHaveBeenCalled();
- done();
- },
- );
- });
- });
-
- describe('setMainEndpoint', () => {
- it('should commit set main endpoint', done => {
- testAction(
- actions.setMainEndpoint,
- 'endpoint',
- state,
- [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }],
- [],
- done,
- );
- });
- });
-
- describe('setIsDeleteDisabled', () => {
- it('should commit set is delete disabled', done => {
- testAction(
- actions.setIsDeleteDisabled,
- true,
- state,
- [{ type: types.SET_IS_DELETE_DISABLED, payload: true }],
- [],
- done,
- );
- });
- });
-
- describe('toggleLoading', () => {
- it('should commit toggle main loading', done => {
- testAction(
- actions.toggleLoading,
- null,
- state,
- [{ type: types.TOGGLE_MAIN_LOADING }],
- [],
- done,
- );
- });
- });
-
- describe('deleteItem and multiDeleteItems', () => {
- let deleted;
- const destroyPath = `${TEST_HOST}/mygroup/myproject/container_registry/1.json`;
-
- const expectDelete = done => {
- expect(mock.history.delete.length).toBe(1);
- expect(deleted).toBe(true);
- done();
- };
-
- beforeEach(() => {
- deleted = false;
- mock.onDelete(destroyPath).replyOnce(() => {
- deleted = true;
- return [200];
- });
- });
-
- it('deleteItem should perform DELETE request on destroyPath', done => {
- testAction(
- actions.deleteItem,
- {
- destroyPath,
- },
- state,
- )
- .then(() => {
- expectDelete(done);
- })
- .catch(done.fail);
- });
-
- it('multiDeleteItems should perform DELETE request on path', done => {
- testAction(actions.multiDeleteItems, { path: destroyPath, items: [1] }, state)
- .then(() => {
- expectDelete(done);
- })
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/frontend/registry/list/stores/getters_spec.js b/spec/frontend/registry/list/stores/getters_spec.js
deleted file mode 100644
index c8d054b226b..00000000000
--- a/spec/frontend/registry/list/stores/getters_spec.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import * as getters from '~/registry/list/stores/getters';
-
-describe('Getters Registry Store', () => {
- let state;
-
- beforeEach(() => {
- state = {
- isLoading: false,
- endpoint: '/root/empty-project/container_registry.json',
- isDeleteDisabled: false,
- repos: [
- {
- canDelete: true,
- destroyPath: 'bar',
- id: '134',
- isLoading: false,
- list: [],
- location: 'foo',
- name: 'gitlab-org/omnibus-gitlab/foo',
- tagsPath: 'foo',
- },
- {
- canDelete: true,
- destroyPath: 'bar',
- id: '123',
- isLoading: false,
- list: [],
- location: 'foo',
- name: 'gitlab-org/omnibus-gitlab',
- tagsPath: 'foo',
- },
- ],
- };
- });
-
- describe('isLoading', () => {
- it('should return the isLoading property', () => {
- expect(getters.isLoading(state)).toEqual(state.isLoading);
- });
- });
-
- describe('repos', () => {
- it('should return the repos', () => {
- expect(getters.repos(state)).toEqual(state.repos);
- });
- });
- describe('isDeleteDisabled', () => {
- it('should return isDeleteDisabled', () => {
- expect(getters.isDeleteDisabled(state)).toEqual(state.isDeleteDisabled);
- });
- });
-});
diff --git a/spec/frontend/registry/list/stores/mutations_spec.js b/spec/frontend/registry/list/stores/mutations_spec.js
deleted file mode 100644
index f894f688c1f..00000000000
--- a/spec/frontend/registry/list/stores/mutations_spec.js
+++ /dev/null
@@ -1,94 +0,0 @@
-import mutations from '~/registry/list/stores/mutations';
-import * as types from '~/registry/list/stores/mutation_types';
-import {
- defaultState,
- reposServerResponse,
- registryServerResponse,
- parsedReposServerResponse,
- parsedRegistryServerResponse,
-} from '../mock_data';
-
-describe('Mutations Registry Store', () => {
- let mockState;
- beforeEach(() => {
- mockState = defaultState;
- });
-
- describe('SET_MAIN_ENDPOINT', () => {
- it('should set the main endpoint', () => {
- const expectedState = Object.assign({}, mockState, { endpoint: 'foo' });
- mutations[types.SET_MAIN_ENDPOINT](mockState, 'foo');
-
- expect(mockState.endpoint).toEqual(expectedState.endpoint);
- });
- });
-
- describe('SET_IS_DELETE_DISABLED', () => {
- it('should set the is delete disabled', () => {
- const expectedState = Object.assign({}, mockState, { isDeleteDisabled: true });
- mutations[types.SET_IS_DELETE_DISABLED](mockState, true);
-
- expect(mockState.isDeleteDisabled).toEqual(expectedState.isDeleteDisabled);
- });
- });
-
- describe('SET_REPOS_LIST', () => {
- it('should set a parsed repository list', () => {
- mutations[types.SET_REPOS_LIST](mockState, reposServerResponse);
-
- expect(mockState.repos).toEqual(parsedReposServerResponse);
- });
- });
-
- describe('TOGGLE_MAIN_LOADING', () => {
- it('should set a parsed repository list', () => {
- mutations[types.TOGGLE_MAIN_LOADING](mockState);
-
- expect(mockState.isLoading).toEqual(true);
- });
- });
-
- describe('SET_REGISTRY_LIST', () => {
- it('should set a list of registries in a specific repository', () => {
- mutations[types.SET_REPOS_LIST](mockState, reposServerResponse);
- mutations[types.SET_REGISTRY_LIST](mockState, {
- repo: mockState.repos[0],
- resp: registryServerResponse,
- headers: {
- 'x-per-page': 2,
- 'x-page': 1,
- 'x-total': 10,
- },
- });
-
- expect(mockState.repos[0].list).toEqual(parsedRegistryServerResponse);
- expect(mockState.repos[0].pagination).toEqual({
- perPage: 2,
- page: 1,
- total: 10,
- totalPages: NaN,
- nextPage: NaN,
- previousPage: NaN,
- });
- });
- });
-
- describe('TOGGLE_REGISTRY_LIST_LOADING', () => {
- it('should toggle isLoading property for a specific repository', () => {
- mutations[types.SET_REPOS_LIST](mockState, reposServerResponse);
- mutations[types.SET_REGISTRY_LIST](mockState, {
- repo: mockState.repos[0],
- resp: registryServerResponse,
- headers: {
- 'x-per-page': 2,
- 'x-page': 1,
- 'x-total': 10,
- },
- });
-
- mutations[types.TOGGLE_REGISTRY_LIST_LOADING](mockState, mockState.repos[0]);
-
- expect(mockState.repos[0].isLoading).toEqual(true);
- });
- });
-});