Migrate issue boards specs to Jest & VTU

Migrated boards specs that rely on tooltips to Jest & Vue Test Utils
parent eafd98ec
import Vue from 'vue';
import IssueTimeEstimate from '~/boards/components/issue_time_estimate.vue';
import boardsStore from '~/boards/stores/boards_store';
import mountComponent from '../../helpers/vue_mount_component_helper';
import { shallowMount } from '@vue/test-utils';
describe('Issue Time Estimate component', () => {
let vm;
let wrapper;
beforeEach(() => {
boardsStore.create();
});
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
describe('when limitToHours is false', () => {
beforeEach(() => {
boardsStore.timeTracking.limitToHours = false;
const Component = Vue.extend(IssueTimeEstimate);
vm = mountComponent(Component, {
wrapper = shallowMount(IssueTimeEstimate, {
propsData: {
estimate: 374460,
},
sync: false,
});
});
it('renders the correct time estimate', () => {
expect(vm.$el.querySelector('time').textContent.trim()).toEqual('2w 3d 1m');
expect(
wrapper
.find('time')
.text()
.trim(),
).toEqual('2w 3d 1m');
});
it('renders expanded time estimate in tooltip', () => {
expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain(
'2 weeks 3 days 1 minute',
);
expect(wrapper.find('.js-issue-time-estimate').text()).toContain('2 weeks 3 days 1 minute');
});
it('prevents tooltip xss', done => {
const alertSpy = spyOn(window, 'alert');
vm.estimate = 'Foo <script>alert("XSS")</script>';
vm.$nextTick(() => {
const alertSpy = jest.spyOn(window, 'alert');
wrapper.setProps({ estimate: 'Foo <script>alert("XSS")</script>' });
wrapper.vm.$nextTick(() => {
expect(alertSpy).not.toHaveBeenCalled();
expect(vm.$el.querySelector('time').textContent.trim()).toEqual('0m');
expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain('0m');
expect(
wrapper
.find('time')
.text()
.trim(),
).toEqual('0m');
expect(wrapper.find('.js-issue-time-estimate').text()).toContain('0m');
done();
});
});
......@@ -50,21 +57,25 @@ describe('Issue Time Estimate component', () => {
describe('when limitToHours is true', () => {
beforeEach(() => {
boardsStore.timeTracking.limitToHours = true;
const Component = Vue.extend(IssueTimeEstimate);
vm = mountComponent(Component, {
wrapper = shallowMount(IssueTimeEstimate, {
propsData: {
estimate: 374460,
},
sync: false,
});
});
it('renders the correct time estimate', () => {
expect(vm.$el.querySelector('time').textContent.trim()).toEqual('104h 1m');
expect(
wrapper
.find('time')
.text()
.trim(),
).toEqual('104h 1m');
});
it('renders expanded time estimate in tooltip', () => {
expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain(
'104 hours 1 minute',
);
expect(wrapper.find('.js-issue-time-estimate').text()).toContain('104 hours 1 minute');
});
});
});
/* global ListAssignee */
/* global ListLabel */
/* global ListIssue */
import Vue from 'vue';
/* global ListAssignee, ListLabel, ListIssue */
import { mount } from '@vue/test-utils';
import _ from 'underscore';
import '~/boards/models/label';
import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
import { listObj } from './mock_data';
import { listObj } from '../../javascripts/boards/mock_data';
import store from '~/boards/stores';
describe('Issue card component', () => {
......@@ -19,6 +16,7 @@ describe('Issue card component', () => {
username: 'test',
avatar: 'test_image',
});
const label1 = new ListLabel({
id: 3,
title: 'testing 123',
......@@ -26,17 +24,13 @@ describe('Issue card component', () => {
text_color: 'white',
description: 'test',
});
let component;
let wrapper;
let issue;
let list;
beforeEach(() => {
setFixtures('<div class="test-container"></div>');
list = {
...listObj,
type: 'label',
};
list = { ...listObj, type: 'label' };
issue = new ListIssue({
title: 'Testing',
id: 1,
......@@ -48,100 +42,91 @@ describe('Issue card component', () => {
real_path: '/test/1',
weight: 1,
});
component = new Vue({
el: document.querySelector('.test-container'),
store,
components: {
'issue-card': IssueCardInner,
},
data() {
return {
wrapper = mount(IssueCardInner, {
propsData: {
list,
issue,
issueLinkBase: '/test',
rootPath: '/',
};
},
template: `
<issue-card
:issue="issue"
:list="list"
:issue-link-base="issueLinkBase"
:root-path="rootPath"></issue-card>
`,
store,
sync: false,
});
});
it('renders issue title', () => {
expect(component.$el.querySelector('.board-card-title').textContent).toContain(issue.title);
expect(wrapper.find('.board-card-title').text()).toContain(issue.title);
});
it('includes issue base in link', () => {
expect(component.$el.querySelector('.board-card-title a').getAttribute('href')).toContain(
'/test',
);
expect(wrapper.find('.board-card-title a').attributes('href')).toContain('/test');
});
it('includes issue title on link', () => {
expect(component.$el.querySelector('.board-card-title a').getAttribute('title')).toBe(
issue.title,
);
expect(wrapper.find('.board-card-title a').attributes('title')).toBe(issue.title);
});
it('does not render confidential icon', () => {
expect(component.$el.querySelector('.fa-eye-flash')).toBeNull();
expect(wrapper.find('.fa-eye-flash').exists()).toBe(false);
});
it('renders confidential icon', done => {
component.issue.confidential = true;
Vue.nextTick(() => {
expect(component.$el.querySelector('.confidential-icon')).not.toBeNull();
wrapper.setProps({
issue: {
...wrapper.props('issue'),
confidential: true,
},
});
wrapper.vm.$nextTick(() => {
expect(wrapper.find('.confidential-icon').exists()).toBe(true);
done();
});
});
it('renders issue ID with #', () => {
expect(component.$el.querySelector('.board-card-number').textContent).toContain(`#${issue.id}`);
expect(wrapper.find('.board-card-number').text()).toContain(`#${issue.id}`);
});
describe('assignee', () => {
it('does not render assignee', () => {
expect(component.$el.querySelector('.board-card-assignee .avatar')).toBeNull();
expect(wrapper.find('.board-card-assignee .avatar').exists()).toBe(false);
});
describe('exists', () => {
beforeEach(done => {
component.issue.assignees = [user];
wrapper.setProps({
issue: {
...wrapper.props('issue'),
assignees: [user],
},
});
Vue.nextTick(() => done());
wrapper.vm.$nextTick(done);
});
it('renders assignee', () => {
expect(component.$el.querySelector('.board-card-assignee .avatar')).not.toBeNull();
expect(wrapper.find('.board-card-assignee .avatar').exists()).toBe(true);
});
it('sets title', () => {
expect(component.$el.querySelector('.js-assignee-tooltip').textContent).toContain(
`${user.name}`,
);
expect(wrapper.find('.js-assignee-tooltip').text()).toContain(`${user.name}`);
});
it('sets users path', () => {
expect(component.$el.querySelector('.board-card-assignee a').getAttribute('href')).toBe(
'/test',
);
expect(wrapper.find('.board-card-assignee a').attributes('href')).toBe('/test');
});
it('renders avatar', () => {
expect(component.$el.querySelector('.board-card-assignee img')).not.toBeNull();
expect(wrapper.find('.board-card-assignee img').exists()).toBe(true);
});
});
describe('assignee default avatar', () => {
beforeEach(done => {
component.issue.assignees = [
wrapper.setProps({
issue: {
...wrapper.props('issue'),
assignees: [
new ListAssignee(
{
id: 1,
......@@ -150,14 +135,16 @@ describe('Issue card component', () => {
},
'default_avatar',
),
];
],
},
});
Vue.nextTick(done);
wrapper.vm.$nextTick(done);
});
it('displays defaults avatar if users avatar is null', () => {
expect(component.$el.querySelector('.board-card-assignee img')).not.toBeNull();
expect(component.$el.querySelector('.board-card-assignee img').getAttribute('src')).toBe(
expect(wrapper.find('.board-card-assignee img').exists()).toBe(true);
expect(wrapper.find('.board-card-assignee img').attributes('src')).toBe(
'default_avatar?width=24',
);
});
......@@ -166,7 +153,10 @@ describe('Issue card component', () => {
describe('multiple assignees', () => {
beforeEach(done => {
component.issue.assignees = [
wrapper.setProps({
issue: {
...wrapper.props('issue'),
assignees: [
new ListAssignee({
id: 2,
name: 'user2',
......@@ -185,18 +175,21 @@ describe('Issue card component', () => {
username: 'user4',
avatar: 'test_image',
}),
];
],
},
});
Vue.nextTick(() => done());
wrapper.vm.$nextTick(done);
});
it('renders all three assignees', () => {
expect(component.$el.querySelectorAll('.board-card-assignee .avatar').length).toEqual(3);
expect(wrapper.findAll('.board-card-assignee .avatar').length).toEqual(3);
});
describe('more than three assignees', () => {
beforeEach(done => {
component.issue.assignees.push(
const { assignees } = wrapper.props('issue');
assignees.push(
new ListAssignee({
id: 5,
name: 'user5',
......@@ -205,33 +198,54 @@ describe('Issue card component', () => {
}),
);
Vue.nextTick(() => done());
wrapper.setProps({
issue: {
...wrapper.props('issue'),
assignees,
},
});
wrapper.vm.$nextTick(done);
});
it('renders more avatar counter', () => {
expect(
component.$el.querySelector('.board-card-assignee .avatar-counter').innerText.trim(),
wrapper
.find('.board-card-assignee .avatar-counter')
.text()
.trim(),
).toEqual('+2');
});
it('renders two assignees', () => {
expect(component.$el.querySelectorAll('.board-card-assignee .avatar').length).toEqual(2);
expect(wrapper.findAll('.board-card-assignee .avatar').length).toEqual(2);
});
it('renders 99+ avatar counter', done => {
for (let i = 5; i < 104; i += 1) {
const u = new ListAssignee({
const assignees = [
...wrapper.props('issue').assignees,
..._.range(5, 103).map(
i =>
new ListAssignee({
id: i,
name: 'name',
username: 'username',
avatar: 'test_image',
}),
),
];
wrapper.setProps({
issue: {
...wrapper.props('issue'),
assignees,
},
});
component.issue.assignees.push(u);
}
Vue.nextTick(() => {
wrapper.vm.$nextTick(() => {
expect(
component.$el.querySelector('.board-card-assignee .avatar-counter').innerText.trim(),
wrapper
.find('.board-card-assignee .avatar-counter')
.text()
.trim(),
).toEqual('99+');
done();
});
......@@ -241,51 +255,50 @@ describe('Issue card component', () => {
describe('labels', () => {
beforeEach(done => {
component.issue.addLabel(label1);
issue.addLabel(label1);
wrapper.setProps({ issue: { ...issue } });
Vue.nextTick(() => done());
wrapper.vm.$nextTick(done);
});
it('does not render list label but renders all other labels', () => {
expect(component.$el.querySelectorAll('.badge').length).toBe(1);
expect(wrapper.findAll('.badge').length).toBe(1);
});
it('renders label', () => {
const nodes = [];
component.$el.querySelectorAll('.badge').forEach(label => {
nodes.push(label.getAttribute('data-original-title'));
});
const nodes = wrapper
.findAll('.badge')
.wrappers.map(label => label.attributes('data-original-title'));
expect(nodes.includes(label1.description)).toBe(true);
});
it('sets label description as title', () => {
expect(component.$el.querySelector('.badge').getAttribute('data-original-title')).toContain(
expect(wrapper.find('.badge').attributes('data-original-title')).toContain(
label1.description,
);
});
it('sets background color of button', () => {
const nodes = [];
component.$el.querySelectorAll('.badge').forEach(label => {
nodes.push(label.style.backgroundColor);
});
const nodes = wrapper
.findAll('.badge')
.wrappers.map(label => label.element.style.backgroundColor);
expect(nodes.includes(label1.color)).toBe(true);
});
it('does not render label if label does not have an ID', done => {
component.issue.addLabel(
issue.addLabel(
new ListLabel({
title: 'closed',
}),
);
Vue.nextTick()
wrapper.setProps({ issue: { ...issue } });
wrapper.vm
.$nextTick()
.then(() => {
expect(component.$el.querySelectorAll('.badge').length).toBe(1);
expect(component.$el.textContent).not.toContain('closed');
expect(wrapper.findAll('.badge').length).toBe(1);
expect(wrapper.text()).not.toContain('closed');
done();
})
.catch(done.fail);
......
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