2020-01-01 13:55:28 +05:30
|
|
|
import { mount } from '@vue/test-utils';
|
2020-05-24 23:13:21 +05:30
|
|
|
import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui';
|
2020-01-01 13:55:28 +05:30
|
|
|
import { trimText } from 'helpers/text_helper';
|
2020-03-13 15:44:24 +05:30
|
|
|
import ReleaseBlockMilestoneInfo from '~/releases/components/release_block_milestone_info.vue';
|
2020-04-08 14:13:33 +05:30
|
|
|
import { milestones as originalMilestones } from '../mock_data';
|
2020-03-13 15:44:24 +05:30
|
|
|
import { MAX_MILESTONES_TO_DISPLAY } from '~/releases/constants';
|
2020-04-08 14:13:33 +05:30
|
|
|
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
2020-01-01 13:55:28 +05:30
|
|
|
|
|
|
|
describe('Release block milestone info', () => {
|
|
|
|
let wrapper;
|
2020-04-08 14:13:33 +05:30
|
|
|
let milestones;
|
2020-01-01 13:55:28 +05:30
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
const factory = props => {
|
2020-01-01 13:55:28 +05:30
|
|
|
wrapper = mount(ReleaseBlockMilestoneInfo, {
|
2020-04-08 14:13:33 +05:30
|
|
|
propsData: props,
|
2020-01-01 13:55:28 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
return wrapper.vm.$nextTick();
|
|
|
|
};
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2020-04-08 14:13:33 +05:30
|
|
|
milestones = convertObjectPropsToCamelCase(originalMilestones, { deep: true });
|
2020-01-01 13:55:28 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
wrapper.destroy();
|
2020-04-08 14:13:33 +05:30
|
|
|
wrapper = null;
|
2020-01-01 13:55:28 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
const milestoneProgressBarContainer = () => wrapper.find('.js-milestone-progress-bar-container');
|
|
|
|
const milestoneListContainer = () => wrapper.find('.js-milestone-list-container');
|
|
|
|
const issuesContainer = () => wrapper.find('.js-issues-container');
|
|
|
|
|
|
|
|
describe('with default props', () => {
|
2020-04-08 14:13:33 +05:30
|
|
|
beforeEach(() => factory({ milestones }));
|
2020-01-01 13:55:28 +05:30
|
|
|
|
|
|
|
it('renders the correct percentage', () => {
|
|
|
|
expect(milestoneProgressBarContainer().text()).toContain('41% complete');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders a progress bar that displays the correct percentage', () => {
|
|
|
|
const progressBar = milestoneProgressBarContainer().find(GlProgressBar);
|
|
|
|
|
|
|
|
expect(progressBar.exists()).toBe(true);
|
|
|
|
expect(progressBar.attributes()).toEqual(
|
|
|
|
expect.objectContaining({
|
|
|
|
value: '22',
|
|
|
|
max: '54',
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders a list of links to all associated milestones', () => {
|
|
|
|
expect(trimText(milestoneListContainer().text())).toContain('Milestones 13.6 • 13.5');
|
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
milestones.forEach((m, i) => {
|
2020-01-01 13:55:28 +05:30
|
|
|
const milestoneLink = milestoneListContainer()
|
|
|
|
.findAll(GlLink)
|
|
|
|
.at(i);
|
|
|
|
|
|
|
|
expect(milestoneLink.text()).toBe(m.title);
|
2020-04-08 14:13:33 +05:30
|
|
|
expect(milestoneLink.attributes('href')).toBe(m.webUrl);
|
2020-03-13 15:44:24 +05:30
|
|
|
expect(milestoneLink.attributes('title')).toBe(m.description);
|
2020-01-01 13:55:28 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the "Issues" section with a total count of issues associated to the milestone(s)', () => {
|
|
|
|
const totalIssueCount = 54;
|
|
|
|
const issuesContainerText = trimText(issuesContainer().text());
|
|
|
|
|
|
|
|
expect(issuesContainerText).toContain(`Issues ${totalIssueCount}`);
|
|
|
|
|
|
|
|
const badge = issuesContainer().find(GlBadge);
|
|
|
|
expect(badge.text()).toBe(totalIssueCount.toString());
|
|
|
|
|
|
|
|
expect(issuesContainerText).toContain('Open: 32 • Closed: 22');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('with lots of milestones', () => {
|
|
|
|
let lotsOfMilestones;
|
|
|
|
let fullListString;
|
|
|
|
let abbreviatedListString;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
lotsOfMilestones = [];
|
2020-04-08 14:13:33 +05:30
|
|
|
const template = milestones[0];
|
2020-01-01 13:55:28 +05:30
|
|
|
|
|
|
|
for (let i = 0; i < MAX_MILESTONES_TO_DISPLAY + 10; i += 1) {
|
|
|
|
lotsOfMilestones.push({
|
|
|
|
...template,
|
|
|
|
id: template.id + i,
|
|
|
|
iid: template.iid + i,
|
|
|
|
title: `m-${i}`,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
fullListString = lotsOfMilestones.map(m => m.title).join(' • ');
|
|
|
|
abbreviatedListString = lotsOfMilestones
|
|
|
|
.slice(0, MAX_MILESTONES_TO_DISPLAY)
|
|
|
|
.map(m => m.title)
|
|
|
|
.join(' • ');
|
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
return factory({ milestones: lotsOfMilestones });
|
2020-01-01 13:55:28 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
const clickShowMoreFewerButton = () => {
|
|
|
|
milestoneListContainer()
|
2020-05-24 23:13:21 +05:30
|
|
|
.find(GlButton)
|
2020-01-01 13:55:28 +05:30
|
|
|
.trigger('click');
|
|
|
|
|
|
|
|
return wrapper.vm.$nextTick();
|
|
|
|
};
|
|
|
|
|
|
|
|
const milestoneListText = () => trimText(milestoneListContainer().text());
|
|
|
|
|
|
|
|
it('only renders a subset of the milestones', () => {
|
|
|
|
expect(milestoneListText()).toContain(`Milestones ${abbreviatedListString} • show 10 more`);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders all milestones when "show more" is clicked', () =>
|
|
|
|
clickShowMoreFewerButton().then(() => {
|
|
|
|
expect(milestoneListText()).toContain(`Milestones ${fullListString} • show fewer`);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('returns to the original view when "show fewer" is clicked', () =>
|
|
|
|
clickShowMoreFewerButton()
|
|
|
|
.then(clickShowMoreFewerButton)
|
|
|
|
.then(() => {
|
|
|
|
expect(milestoneListText()).toContain(
|
|
|
|
`Milestones ${abbreviatedListString} • show 10 more`,
|
|
|
|
);
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
const expectAllZeros = () => {
|
|
|
|
it('displays percentage as 0%', () => {
|
|
|
|
expect(milestoneProgressBarContainer().text()).toContain('0% complete');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('shows 0 for all issue counts', () => {
|
|
|
|
const issuesContainerText = trimText(issuesContainer().text());
|
|
|
|
|
|
|
|
expect(issuesContainerText).toContain('Issues 0 Open: 0 • Closed: 0');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Ensures we don't have any issues with dividing by zero when computing percentages */
|
|
|
|
describe('when all issue counts are zero', () => {
|
|
|
|
beforeEach(() => {
|
2020-04-08 14:13:33 +05:30
|
|
|
milestones = milestones.map(m => ({
|
2020-01-01 13:55:28 +05:30
|
|
|
...m,
|
2020-04-08 14:13:33 +05:30
|
|
|
issueStats: {
|
|
|
|
...m.issueStats,
|
|
|
|
total: 0,
|
2020-01-01 13:55:28 +05:30
|
|
|
closed: 0,
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
return factory({ milestones });
|
2020-01-01 13:55:28 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
expectAllZeros();
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('if the API response is missing the "issue_stats" property', () => {
|
|
|
|
beforeEach(() => {
|
2020-04-08 14:13:33 +05:30
|
|
|
milestones = milestones.map(m => ({
|
2020-01-01 13:55:28 +05:30
|
|
|
...m,
|
2020-04-08 14:13:33 +05:30
|
|
|
issueStats: undefined,
|
2020-01-01 13:55:28 +05:30
|
|
|
}));
|
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
return factory({ milestones });
|
2020-01-01 13:55:28 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
expectAllZeros();
|
|
|
|
});
|
2020-04-08 14:13:33 +05:30
|
|
|
|
|
|
|
describe('Issue links', () => {
|
|
|
|
const findOpenIssuesLink = () => wrapper.find({ ref: 'openIssuesLink' });
|
|
|
|
const findOpenIssuesText = () => wrapper.find({ ref: 'openIssuesText' });
|
|
|
|
const findClosedIssuesLink = () => wrapper.find({ ref: 'closedIssuesLink' });
|
|
|
|
const findClosedIssuesText = () => wrapper.find({ ref: 'closedIssuesText' });
|
|
|
|
|
|
|
|
describe('when openIssuePath is provided', () => {
|
|
|
|
const openIssuesPath = '/path/to/open/issues';
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
return factory({ milestones, openIssuesPath });
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the open issues as a link', () => {
|
|
|
|
expect(findOpenIssuesLink().exists()).toBe(true);
|
|
|
|
expect(findOpenIssuesText().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the open issues link with the correct href', () => {
|
|
|
|
expect(findOpenIssuesLink().attributes().href).toBe(openIssuesPath);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when openIssuePath is not provided', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
return factory({ milestones });
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the open issues as plain text', () => {
|
|
|
|
expect(findOpenIssuesLink().exists()).toBe(false);
|
|
|
|
expect(findOpenIssuesText().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when closedIssuePath is provided', () => {
|
|
|
|
const closedIssuesPath = '/path/to/closed/issues';
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
return factory({ milestones, closedIssuesPath });
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the closed issues as a link', () => {
|
|
|
|
expect(findClosedIssuesLink().exists()).toBe(true);
|
|
|
|
expect(findClosedIssuesText().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the closed issues link with the correct href', () => {
|
|
|
|
expect(findClosedIssuesLink().attributes().href).toBe(closedIssuesPath);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when closedIssuePath is not provided', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
return factory({ milestones });
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the closed issues as plain text', () => {
|
|
|
|
expect(findClosedIssuesLink().exists()).toBe(false);
|
|
|
|
expect(findClosedIssuesText().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2020-01-01 13:55:28 +05:30
|
|
|
});
|