summaryrefslogtreecommitdiff
path: root/spec/frontend/releases
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/releases')
-rw-r--r--spec/frontend/releases/detail/components/app_spec.js2
-rw-r--r--spec/frontend/releases/detail/store/actions_spec.js2
-rw-r--r--spec/frontend/releases/list/components/evidence_block_spec.js77
-rw-r--r--spec/frontend/releases/list/components/release_block_footer_spec.js12
-rw-r--r--spec/frontend/releases/list/components/release_block_milestone_info_spec.js179
-rw-r--r--spec/frontend/releases/list/components/release_block_spec.js119
-rw-r--r--spec/frontend/releases/mock_data.js11
7 files changed, 345 insertions, 57 deletions
diff --git a/spec/frontend/releases/detail/components/app_spec.js b/spec/frontend/releases/detail/components/app_spec.js
index 4726f18c8fa..4f094e8639a 100644
--- a/spec/frontend/releases/detail/components/app_spec.js
+++ b/spec/frontend/releases/detail/components/app_spec.js
@@ -29,7 +29,7 @@ describe('Release detail component', () => {
const store = new Vuex.Store({ actions, state });
- wrapper = mount(ReleaseDetailApp, { store });
+ wrapper = mount(ReleaseDetailApp, { store, sync: false, attachToDocument: true });
return wrapper.vm.$nextTick();
});
diff --git a/spec/frontend/releases/detail/store/actions_spec.js b/spec/frontend/releases/detail/store/actions_spec.js
index f1c7f3c1048..5a1447aa4fc 100644
--- a/spec/frontend/releases/detail/store/actions_spec.js
+++ b/spec/frontend/releases/detail/store/actions_spec.js
@@ -1,7 +1,7 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import * as actions from '~/releases/detail/store/actions';
import testAction from 'helpers/vuex_action_helper';
+import * as actions from '~/releases/detail/store/actions';
import * as types from '~/releases/detail/store/mutation_types';
import { release } from '../../mock_data';
import state from '~/releases/detail/store/state';
diff --git a/spec/frontend/releases/list/components/evidence_block_spec.js b/spec/frontend/releases/list/components/evidence_block_spec.js
new file mode 100644
index 00000000000..e8a3eace216
--- /dev/null
+++ b/spec/frontend/releases/list/components/evidence_block_spec.js
@@ -0,0 +1,77 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
+import { truncateSha } from '~/lib/utils/text_utility';
+import Icon from '~/vue_shared/components/icon.vue';
+import { release } from '../../mock_data';
+import EvidenceBlock from '~/releases/list/components/evidence_block.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+describe('Evidence Block', () => {
+ let wrapper;
+
+ const factory = (options = {}) => {
+ const localVue = createLocalVue();
+
+ wrapper = mount(localVue.extend(EvidenceBlock), {
+ localVue,
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ factory({
+ propsData: {
+ release,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the evidence icon', () => {
+ expect(wrapper.find(Icon).props('name')).toBe('review-list');
+ });
+
+ it('renders the title for the dowload link', () => {
+ expect(wrapper.find(GlLink).text()).toBe(`${release.tag_name}-evidence.json`);
+ });
+
+ it('renders the correct hover text for the download', () => {
+ expect(wrapper.find(GlLink).attributes('data-original-title')).toBe('Download evidence JSON');
+ });
+
+ it('renders the correct file link for download', () => {
+ expect(wrapper.find(GlLink).attributes().download).toBe(`${release.tag_name}-evidence.json`);
+ });
+
+ describe('sha text', () => {
+ it('renders the short sha initially', () => {
+ expect(wrapper.find('.js-short').text()).toBe(truncateSha(release.evidence_sha));
+ });
+
+ it('renders the long sha after expansion', () => {
+ wrapper.find('.js-text-expander-prepend').trigger('click');
+ expect(wrapper.find('.js-expanded').text()).toBe(release.evidence_sha);
+ });
+ });
+
+ describe('copy to clipboard button', () => {
+ it('renders button', () => {
+ expect(wrapper.find(ClipboardButton).exists()).toBe(true);
+ });
+
+ it('renders the correct hover text', () => {
+ expect(wrapper.find(ClipboardButton).attributes('data-original-title')).toBe(
+ 'Copy commit SHA',
+ );
+ });
+
+ it('copies the sha', () => {
+ expect(wrapper.find(ClipboardButton).attributes('data-clipboard-text')).toBe(
+ release.evidence_sha,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/releases/list/components/release_block_footer_spec.js b/spec/frontend/releases/list/components/release_block_footer_spec.js
index 172147f1cc8..7652acbdd62 100644
--- a/spec/frontend/releases/list/components/release_block_footer_spec.js
+++ b/spec/frontend/releases/list/components/release_block_footer_spec.js
@@ -1,15 +1,15 @@
import { mount } from '@vue/test-utils';
-import ReleaseBlockFooter from '~/releases/list/components/release_block_footer.vue';
-import Icon from '~/vue_shared/components/icon.vue';
import { GlLink } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper';
+import ReleaseBlockFooter from '~/releases/list/components/release_block_footer.vue';
+import Icon from '~/vue_shared/components/icon.vue';
import { release } from '../../mock_data';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
jest.mock('~/vue_shared/mixins/timeago', () => ({
methods: {
- timeFormated() {
- return '7 fortnightes ago';
+ timeFormatted() {
+ return '7 fortnights ago';
},
tooltipTitle() {
return 'February 30, 2401';
@@ -82,7 +82,7 @@ describe('Release block footer', () => {
it('renders the author and creation time info', () => {
expect(trimText(authorDateInfoSection().text())).toBe(
- `Created 7 fortnightes ago by ${releaseClone.author.username}`,
+ `Created 7 fortnights ago by ${releaseClone.author.username}`,
);
});
@@ -139,7 +139,7 @@ describe('Release block footer', () => {
beforeEach(() => factory({ author: undefined }));
it('renders the release date without the author name', () => {
- expect(trimText(authorDateInfoSection().text())).toBe('Created 7 fortnightes ago');
+ expect(trimText(authorDateInfoSection().text())).toBe('Created 7 fortnights ago');
});
});
diff --git a/spec/frontend/releases/list/components/release_block_milestone_info_spec.js b/spec/frontend/releases/list/components/release_block_milestone_info_spec.js
new file mode 100644
index 00000000000..7179ab3d3cc
--- /dev/null
+++ b/spec/frontend/releases/list/components/release_block_milestone_info_spec.js
@@ -0,0 +1,179 @@
+import { mount } from '@vue/test-utils';
+import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui';
+import { trimText } from 'helpers/text_helper';
+import ReleaseBlockMilestoneInfo from '~/releases/list/components/release_block_milestone_info.vue';
+import { milestones } from '../../mock_data';
+import { MAX_MILESTONES_TO_DISPLAY } from '~/releases/list/constants';
+
+describe('Release block milestone info', () => {
+ let wrapper;
+ let milestonesClone;
+
+ const factory = milestonesProp => {
+ wrapper = mount(ReleaseBlockMilestoneInfo, {
+ propsData: {
+ milestones: milestonesProp,
+ },
+ sync: false,
+ });
+
+ return wrapper.vm.$nextTick();
+ };
+
+ beforeEach(() => {
+ milestonesClone = JSON.parse(JSON.stringify(milestones));
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ 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', () => {
+ beforeEach(() => factory(milestonesClone));
+
+ 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');
+
+ milestonesClone.forEach((m, i) => {
+ const milestoneLink = milestoneListContainer()
+ .findAll(GlLink)
+ .at(i);
+
+ expect(milestoneLink.text()).toBe(m.title);
+ expect(milestoneLink.attributes('href')).toBe(m.web_url);
+ expect(milestoneLink.attributes('data-original-title')).toBe(m.description);
+ });
+ });
+
+ 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 = [];
+ const template = milestonesClone[0];
+
+ 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(' • ');
+
+ return factory(lotsOfMilestones);
+ });
+
+ const clickShowMoreFewerButton = () => {
+ milestoneListContainer()
+ .find(GlButton)
+ .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(() => {
+ milestonesClone = milestonesClone.map(m => ({
+ ...m,
+ issue_stats: {
+ ...m.issue_stats,
+ opened: 0,
+ closed: 0,
+ },
+ }));
+
+ return factory(milestonesClone);
+ });
+
+ expectAllZeros();
+ });
+
+ describe('if the API response is missing the "issue_stats" property', () => {
+ beforeEach(() => {
+ milestonesClone = milestonesClone.map(m => ({
+ ...m,
+ issue_stats: undefined,
+ }));
+
+ return factory(milestonesClone);
+ });
+
+ expectAllZeros();
+ });
+});
diff --git a/spec/frontend/releases/list/components/release_block_spec.js b/spec/frontend/releases/list/components/release_block_spec.js
index b63ef068d8e..38c5e4fc0a2 100644
--- a/spec/frontend/releases/list/components/release_block_spec.js
+++ b/spec/frontend/releases/list/components/release_block_spec.js
@@ -1,8 +1,9 @@
import { mount } from '@vue/test-utils';
+import { first } from 'underscore';
+import EvidenceBlock from '~/releases/list/components/evidence_block.vue';
import ReleaseBlock from '~/releases/list/components/release_block.vue';
import ReleaseBlockFooter from '~/releases/list/components/release_block_footer.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
-import { first } from 'underscore';
import { release } from '../../mock_data';
import Icon from '~/vue_shared/components/icon.vue';
import { scrollToElement } from '~/lib/utils/common_utils';
@@ -29,7 +30,6 @@ describe('Release block', () => {
},
provide: {
glFeatures: {
- releaseEditPage: true,
releaseIssueSummary: true,
...featureFlags,
},
@@ -68,7 +68,7 @@ describe('Release block', () => {
});
it('renders release date', () => {
- expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormated(release.released_at));
+ expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormatted(release.released_at));
});
it('renders number of assets provided', () => {
@@ -117,35 +117,6 @@ describe('Release block', () => {
});
});
- it('renders the milestone icon', () => {
- expect(
- milestoneListLabel()
- .find(Icon)
- .exists(),
- ).toBe(true);
- });
-
- it('renders the label as "Milestones" if more than one milestone is passed in', () => {
- expect(
- milestoneListLabel()
- .find('.js-label-text')
- .text(),
- ).toEqual('Milestones');
- });
-
- it('renders a link to the milestone with a tooltip', () => {
- const milestone = first(release.milestones);
- const milestoneLink = wrapper.find('.js-milestone-link');
-
- expect(milestoneLink.exists()).toBe(true);
-
- expect(milestoneLink.text()).toBe(milestone.title);
-
- expect(milestoneLink.attributes('href')).toBe(milestone.web_url);
-
- expect(milestoneLink.attributes('data-original-title')).toBe(milestone.description);
- });
-
it('renders the footer', () => {
expect(wrapper.find(ReleaseBlockFooter).exists()).toBe(true);
});
@@ -179,11 +150,6 @@ describe('Release block', () => {
});
});
- it('does not render an edit button if the releaseEditPage feature flag is disabled', () =>
- factory(releaseClone, { releaseEditPage: false }).then(() => {
- expect(editButton().exists()).toBe(false);
- }));
-
it('does not render the milestone list if no milestones are associated to the release', () => {
delete releaseClone.milestones;
@@ -192,18 +158,6 @@ describe('Release block', () => {
});
});
- it('renders the label as "Milestone" if only a single milestone is passed in', () => {
- releaseClone.milestones = releaseClone.milestones.slice(0, 1);
-
- return factory(releaseClone).then(() => {
- expect(
- milestoneListLabel()
- .find('.js-label-text')
- .text(),
- ).toEqual('Milestone');
- });
- });
-
it('renders upcoming release badge', () => {
releaseClone.upcoming_release = true;
@@ -220,6 +174,26 @@ describe('Release block', () => {
});
});
+ describe('evidence block', () => {
+ it('renders the evidence block when the evidence is available and the feature flag is true', () =>
+ factory(releaseClone, { releaseEvidenceCollection: true }).then(() =>
+ expect(wrapper.find(EvidenceBlock).exists()).toBe(true),
+ ));
+
+ it('does not render the evidence block when the evidence is available but the feature flag is false', () =>
+ factory(releaseClone, { releaseEvidenceCollection: true }).then(() =>
+ expect(wrapper.find(EvidenceBlock).exists()).toBe(true),
+ ));
+
+ it('does not render the evidence block when there is no evidence', () => {
+ releaseClone.evidence_sha = null;
+
+ return factory(releaseClone).then(() => {
+ expect(wrapper.find(EvidenceBlock).exists()).toBe(false);
+ });
+ });
+ });
+
describe('anchor scrolling', () => {
beforeEach(() => {
scrollToElement.mockClear();
@@ -266,4 +240,51 @@ describe('Release block', () => {
});
});
});
+
+ describe('when the releaseIssueSummary feature flag is disabled', () => {
+ describe('with default props', () => {
+ beforeEach(() => factory(release, { releaseIssueSummary: false }));
+
+ it('renders the milestone icon', () => {
+ expect(
+ milestoneListLabel()
+ .find(Icon)
+ .exists(),
+ ).toBe(true);
+ });
+
+ it('renders the label as "Milestones" if more than one milestone is passed in', () => {
+ expect(
+ milestoneListLabel()
+ .find('.js-label-text')
+ .text(),
+ ).toEqual('Milestones');
+ });
+
+ it('renders a link to the milestone with a tooltip', () => {
+ const milestone = first(release.milestones);
+ const milestoneLink = wrapper.find('.js-milestone-link');
+
+ expect(milestoneLink.exists()).toBe(true);
+
+ expect(milestoneLink.text()).toBe(milestone.title);
+
+ expect(milestoneLink.attributes('href')).toBe(milestone.web_url);
+
+ expect(milestoneLink.attributes('data-original-title')).toBe(milestone.description);
+ });
+ });
+
+ it('renders the label as "Milestone" if only a single milestone is passed in', () => {
+ releaseClone.milestones = releaseClone.milestones.slice(0, 1);
+
+ return factory(releaseClone, { releaseIssueSummary: false }).then(() => {
+ expect(
+ milestoneListLabel()
+ .find('.js-label-text')
+ .text(),
+ ).toEqual('Milestone');
+ });
+ });
+ });
});
diff --git a/spec/frontend/releases/mock_data.js b/spec/frontend/releases/mock_data.js
index 61d95b86b1c..630f14d3a73 100644
--- a/spec/frontend/releases/mock_data.js
+++ b/spec/frontend/releases/mock_data.js
@@ -11,6 +11,10 @@ export const milestones = [
due_date: '2019-09-19',
start_date: '2019-08-31',
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/2',
+ issue_stats: {
+ opened: 14,
+ closed: 19,
+ },
},
{
id: 49,
@@ -24,6 +28,10 @@ export const milestones = [
due_date: '2019-10-11',
start_date: '2019-08-19',
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/1',
+ issue_stats: {
+ opened: 18,
+ closed: 3,
+ },
},
];
@@ -35,6 +43,7 @@ export const release = {
description_html: '<p data-sourcepos="1:1-1:21" dir="auto">A super nice release!</p>',
created_at: '2019-08-26T17:54:04.952Z',
released_at: '2019-08-26T17:54:04.807Z',
+ evidence_sha: 'fb3a125fd69a0e5048ebfb0ba43eb32ce4911520dd8d',
author: {
id: 1,
name: 'Administrator',
@@ -62,6 +71,8 @@ export const release = {
milestones,
assets: {
count: 5,
+ evidence_file_path:
+ 'https://20592.qa-tunnel.gitlab.info/root/test-deployments/-/releases/v1.1.2/evidence.json',
sources: [
{
format: 'zip',