diff options
Diffstat (limited to 'spec/frontend/releases')
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', |