Commit 7e2bd5ec authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Phil Hughes

Refactor board list to jest

- add sortablejs mock
- remove common file
- refactor to jest
- remove old test
parent c5e82147
import Sortablejs from 'sortablejs';
export default Sortablejs;
export const Sortable = Sortablejs;
export class MultiDrag {}
......@@ -2,9 +2,11 @@
/* global ListAssignee */
/* global ListLabel */
import Vue from 'vue';
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';
......@@ -13,22 +15,41 @@ 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 vm;
let wrapper;
let mock;
let list;
beforeEach(done => {
mock = new MockAdapter(axios);
mock.onAny().reply(boardsMockInterceptor);
setMockEndpoints();
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 BoardCardComp = Vue.extend(boardCard);
const list = new List(listObj);
const label1 = new ListLabel({
id: 3,
title: 'testing 123',
......@@ -36,178 +57,155 @@ describe('Board card', () => {
text_color: 'white',
description: 'test',
});
setTimeout(() => {
return waitForPromises().then(() => {
list.issues[0].labels.push(label1);
});
};
vm = new BoardCardComp({
store,
propsData: {
list,
issue: list.issues[0],
issueLinkBase: '/',
disabled: false,
index: 0,
rootPath: '/',
},
}).$mount();
done();
}, 0);
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onAny().reply(boardsMockInterceptor);
setMockEndpoints();
return setupData();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
list = null;
mock.restore();
});
it('returns false when detailIssue is empty', () => {
expect(vm.issueDetailVisible).toBe(false);
it('when details issue is empty does not show the element', () => {
mountComponent();
expect(wrapper.classes()).not.toContain('is-active');
});
it('returns true when detailIssue is equal to card issue', () => {
boardsStore.detail.issue = vm.issue;
it('when detailIssue is equal to card issue shows the element', () => {
[boardsStore.detail.issue] = list.issues;
mountComponent();
expect(vm.issueDetailVisible).toBe(true);
expect(wrapper.classes()).toContain('is-active');
});
it("returns false when multiSelect doesn't contain issue", () => {
expect(vm.multiSelectVisible).toBe(false);
it('when multiSelect does not contain issue removes multi select class', () => {
mountComponent();
expect(wrapper.classes()).not.toContain('multi-select');
});
it('returns true when multiSelect contains issue', () => {
boardsStore.multiSelect.list = [vm.issue];
it('when multiSelect contain issue add multi select class', () => {
boardsStore.multiSelect.list = [list.issues[0]];
mountComponent();
expect(vm.multiSelectVisible).toBe(true);
expect(wrapper.classes()).toContain('multi-select');
});
it('adds user-can-drag class if not disabled', () => {
expect(vm.$el.classList.contains('user-can-drag')).toBe(true);
mountComponent();
expect(wrapper.classes()).toContain('user-can-drag');
});
it('does not add user-can-drag class disabled', done => {
vm.disabled = true;
it('does not add user-can-drag class disabled', () => {
mountComponent({ disabled: true });
setTimeout(() => {
expect(vm.$el.classList.contains('user-can-drag')).toBe(false);
done();
}, 0);
expect(wrapper.classes()).not.toContain('user-can-drag');
});
it('does not add disabled class', () => {
expect(vm.$el.classList.contains('is-disabled')).toBe(false);
mountComponent();
expect(wrapper.classes()).not.toContain('is-disabled');
});
it('adds disabled class is disabled is true', done => {
vm.disabled = true;
it('adds disabled class is disabled is true', () => {
mountComponent({ disabled: true });
setTimeout(() => {
expect(vm.$el.classList.contains('is-disabled')).toBe(true);
done();
}, 0);
expect(wrapper.classes()).toContain('is-disabled');
});
describe('mouse events', () => {
const triggerEvent = (eventName, el = vm.$el) => {
const event = document.createEvent('MouseEvents');
event.initMouseEvent(
eventName,
true,
true,
window,
1,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null,
);
el.dispatchEvent(event);
};
it('sets showDetail to true on mousedown', () => {
triggerEvent('mousedown');
expect(vm.showDetail).toBe(true);
mountComponent();
wrapper.trigger('mousedown');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.showDetail).toBe(true);
});
});
it('sets showDetail to false on mousemove', () => {
triggerEvent('mousedown');
expect(vm.showDetail).toBe(true);
triggerEvent('mousemove');
expect(vm.showDetail).toBe(false);
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', () => {
triggerEvent('mouseup', vm.$el.querySelector('a'));
mountComponent();
findIssueCardInner()
.find('a')
.trigger('mouseup');
expect(boardsStore.detail.issue).toEqual({});
});
it('does not set detail issue if img is clicked', done => {
vm.issue.assignees = [
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',
}),
];
],
},
});
Vue.nextTick(() => {
triggerEvent('mouseup', vm.$el.querySelector('img'));
findUserAvatarLink().trigger('mouseup');
expect(boardsStore.detail.issue).toEqual({});
done();
});
});
it('does not set detail issue if showDetail is false after mouseup', () => {
triggerEvent('mouseup');
mountComponent();
wrapper.trigger('mouseup');
expect(boardsStore.detail.issue).toEqual({});
});
it('sets detail issue to card issue on mouse up', () => {
spyOn(eventHub, '$emit');
triggerEvent('mousedown');
triggerEvent('mouseup');
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', vm.issue, undefined);
expect(boardsStore.detail.list).toEqual(vm.list);
});
mountComponent();
it('adds active class if detail issue is set', done => {
vm.detailIssue.issue = vm.issue;
wrapper.trigger('mousedown');
wrapper.trigger('mouseup');
Vue.nextTick()
.then(() => {
expect(vm.$el.classList.contains('is-active')).toBe(true);
})
.then(done)
.catch(done.fail);
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', () => {
spyOn(eventHub, '$emit');
boardsStore.detail.issue = vm.issue;
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
[boardsStore.detail.issue] = list.issues;
mountComponent();
triggerEvent('mousedown');
triggerEvent('mouseup');
wrapper.trigger('mousedown');
wrapper.trigger('mouseup');
expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue', undefined);
});
......
/* 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 createComponent from './board_list_common_spec';
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;
......@@ -21,7 +72,7 @@ describe('Board list component', () => {
describe('When Expanded', () => {
beforeEach(done => {
getIssues = spyOn(List.prototype, 'getIssues').and.returnValue(new Promise(() => {}));
getIssues = jest.spyOn(List.prototype, 'getIssues').mockReturnValue(new Promise(() => {}));
({ mock, component } = createComponent({ done }));
});
......@@ -30,26 +81,21 @@ describe('Board list component', () => {
component.$destroy();
});
it('loads first page of issues', done => {
waitForPromises()
.then(() => {
it('loads first page of issues', () => {
return waitForPromises().then(() => {
expect(getIssues).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
});
});
it('renders component', () => {
expect(component.$el.classList.contains('board-list-component')).toBe(true);
});
it('renders loading icon', done => {
it('renders loading icon', () => {
component.loading = true;
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.board-list-loading')).not.toBeNull();
done();
});
});
......@@ -61,135 +107,110 @@ describe('Board list component', () => {
expect(component.$el.querySelector('.board-card').getAttribute('data-issue-id')).toBe('1');
});
it('shows new issue form', done => {
it('shows new issue form', () => {
component.toggleForm();
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull();
expect(component.$el.querySelector('.is-smaller')).not.toBeNull();
done();
});
});
it('shows new issue form after eventhub event', done => {
it('shows new issue form after eventhub event', () => {
eventHub.$emit(`hide-issue-form-${component.list.id}`);
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull();
expect(component.$el.querySelector('.is-smaller')).not.toBeNull();
done();
});
});
it('does not show new issue form for closed list', done => {
it('does not show new issue form for closed list', () => {
component.list.type = 'closed';
component.toggleForm();
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.board-new-issue-form')).toBeNull();
done();
});
});
it('shows count list item', done => {
it('shows count list item', () => {
component.showCount = true;
Vue.nextTick(() => {
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',
);
done();
});
});
it('sets data attribute with invalid id', done => {
it('sets data attribute with invalid id', () => {
component.showCount = true;
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.board-list-count').getAttribute('data-issue-id')).toBe(
'-1',
);
done();
});
});
it('shows how many more issues to load', done => {
it('shows how many more issues to load', () => {
component.showCount = true;
component.list.issuesSize = 20;
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe(
'Showing 1 of 20 issues',
);
done();
});
});
it('loads more issues after scrolling', done => {
spyOn(component.list, 'nextPage');
component.$refs.list.style.height = '100px';
component.$refs.list.style.overflow = 'scroll';
it('loads more issues after scrolling', () => {
jest.spyOn(component.list, 'nextPage').mockImplementation(() => {});
generateIssues(component);
component.$refs.list.dispatchEvent(new Event('scroll'));
Vue.nextTick(() => {
component.$refs.list.scrollTop = 20000;
waitForPromises()
.then(() => {
return waitForPromises().then(() => {
expect(component.list.nextPage).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
});
});
it('does not load issues if already loading', done => {
component.list.nextPage = spyOn(component.list, 'nextPage').and.returnValue(
new Promise(() => {}),
);
it('does not load issues if already loading', () => {
component.list.nextPage = jest
.spyOn(component.list, 'nextPage')
.mockReturnValue(new Promise(() => {}));
component.onScroll();
component.onScroll();
waitForPromises()
.then(() => {
return waitForPromises().then(() => {
expect(component.list.nextPage).toHaveBeenCalledTimes(1);
})
.then(done)
.catch(done.fail);
});
});
it('shows loading more spinner', done => {
it('shows loading more spinner', () => {
component.showCount = true;
component.list.loadingMore = true;
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull();
done();
});
});
});
describe('When Collapsed', () => {
beforeEach(done => {
getIssues = spyOn(List.prototype, 'getIssues').and.returnValue(new Promise(() => {}));
getIssues = jest.spyOn(List.prototype, 'getIssues').mockReturnValue(new Promise(() => {}));
({ mock, component } = createComponent({
done,
listProps: { type: 'closed', collapsed: true, issuesSize: 50 },
}));
generateIssues(component);
component.scrollHeight = spyOn(component, 'scrollHeight').and.returnValue(0);
component.scrollHeight = jest.spyOn(component, 'scrollHeight').mockReturnValue(0);
});
afterEach(() => {
......@@ -197,14 +218,11 @@ describe('Board list component', () => {
component.$destroy();
});
it('does not load all issues', done => {
waitForPromises()
.then(() => {
it('does not load all issues', () => {
return waitForPromises().then(() => {
// Initial getIssues from list constructor
expect(getIssues).toHaveBeenCalledTimes(1);
})
.then(done)
.catch(done.fail);
});
});
});
......@@ -222,39 +240,33 @@ describe('Board list component', () => {
});
describe('when issue count exceeds max issue count', () => {
it('sets background to bg-danger-100', done => {
it('sets background to bg-danger-100', () => {
component.list.issuesSize = 4;
component.list.maxIssueCount = 3;
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.bg-danger-100')).not.toBeNull();
done();
});
});
});
describe('when list issue count does NOT exceed list max issue count', () => {
it('does not sets background to bg-danger-100', done => {
it('does not sets background to bg-danger-100', () => {
component.list.issuesSize = 2;
component.list.maxIssueCount = 3;
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.bg-danger-100')).toBeNull();
done();
});
});
});
describe('when list max issue count is 0', () => {
it('does not sets background to bg-danger-100', done => {
it('does not sets background to bg-danger-100', () => {
component.list.maxIssueCount = 0;
Vue.nextTick(() => {
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.bg-danger-100')).toBeNull();
done();
});
});
});
......
......@@ -9,7 +9,9 @@ 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', () => {
......@@ -20,22 +22,35 @@ describe('List model', () => {
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();
});
it('gets issues when created', done => {
setTimeout(() => {
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);
done();
}, 0);
});
it('saves list and returns ID', done => {
it('saves list and returns ID', () => {
list = new List({
title: 'test',
label: {
......@@ -45,50 +60,40 @@ describe('List model', () => {
text_color: 'white',
},
});
list.save();
setTimeout(() => {
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');
done();
}, 0);
});
});
it('destroys the list', done => {
it('destroys the list', () => {
boardsStore.addList(listObj);
list = boardsStore.findList('id', listObj.id);
expect(boardsStore.state.lists.length).toBe(1);
list.destroy();
setTimeout(() => {
return waitForPromises().then(() => {
expect(boardsStore.state.lists.length).toBe(0);
done();
}, 0);
});
});
it('gets issue from list', done => {
setTimeout(() => {
it('gets issue from list', () => {
const issue = list.findIssue(1);
expect(issue).toBeDefined();
done();
}, 0);
});
it('removes issue', done => {
setTimeout(() => {
it('removes issue', () => {
const issue = list.findIssue(1);
expect(list.issues.length).toBe(1);
list.removeIssue(issue);
expect(list.issues.length).toBe(0);
done();
}, 0);
});
it('sends service request to update issue label', () => {
......@@ -105,7 +110,7 @@ describe('List model', () => {
list.issues.push(issue);
listDup.issues.push(issue);
spyOn(boardsStore, 'moveIssue').and.callThrough();
jest.spyOn(boardsStore, 'moveIssue');
listDup.updateIssueLabel(issue, list);
......@@ -120,7 +125,8 @@ describe('List model', () => {
describe('page number', () => {
beforeEach(() => {
spyOn(list, 'getIssues');
jest.spyOn(list, 'getIssues').mockImplementation(() => {});
list.issues = [];
});
it('increase page number if current issue count is more than the page size', () => {
......@@ -167,7 +173,7 @@ describe('List model', () => {
describe('newIssue', () => {
beforeEach(() => {
spyOn(boardsStore, 'newIssue').and.returnValue(
jest.spyOn(boardsStore, 'newIssue').mockReturnValue(
Promise.resolve({
data: {
id: 42,
......@@ -178,6 +184,7 @@ describe('List model', () => {
},
}),
);
list.issues = [];
});
it('adds new issue to top of list', done => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment