diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-15 00:09:08 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-15 00:09:08 +0300 |
commit | 866ca4e49ff74ffadf8e6f6ff663a168489c2aba (patch) | |
tree | cc3135b1bae11dbd1cb3a30cb547473ad89a5551 /spec/frontend/releases/components | |
parent | 26a50872e9da9509c52c70f74dc21698fec906db (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/releases/components')
6 files changed, 864 insertions, 0 deletions
diff --git a/spec/frontend/releases/components/app_edit_spec.js b/spec/frontend/releases/components/app_edit_spec.js new file mode 100644 index 00000000000..cb940facbd6 --- /dev/null +++ b/spec/frontend/releases/components/app_edit_spec.js @@ -0,0 +1,97 @@ +import Vuex from 'vuex'; +import { mount } from '@vue/test-utils'; +import ReleaseEditApp from '~/releases/components/app_edit.vue'; +import { release } from '../mock_data'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; + +describe('Release edit component', () => { + let wrapper; + let releaseClone; + let actions; + let state; + + beforeEach(() => { + gon.api_version = 'v4'; + + releaseClone = JSON.parse(JSON.stringify(convertObjectPropsToCamelCase(release))); + + state = { + release: releaseClone, + markdownDocsPath: 'path/to/markdown/docs', + updateReleaseApiDocsPath: 'path/to/update/release/api/docs', + }; + + actions = { + fetchRelease: jest.fn(), + updateRelease: jest.fn(), + navigateToReleasesPage: jest.fn(), + }; + + const store = new Vuex.Store({ + modules: { + detail: { + namespaced: true, + actions, + state, + }, + }, + }); + + wrapper = mount(ReleaseEditApp, { + store, + }); + + return wrapper.vm.$nextTick(); + }); + + it('calls fetchRelease when the component is created', () => { + expect(actions.fetchRelease).toHaveBeenCalledTimes(1); + }); + + it('renders the description text at the top of the page', () => { + expect(wrapper.find('.js-subtitle-text').text()).toBe( + 'Releases are based on Git tags. We recommend naming tags that fit within semantic versioning, for example v1.0, v2.0-pre.', + ); + }); + + it('renders the correct tag name in the "Tag name" field', () => { + expect(wrapper.find('#git-ref').element.value).toBe(releaseClone.tagName); + }); + + it('renders the correct help text under the "Tag name" field', () => { + const helperText = wrapper.find('#tag-name-help'); + const helperTextLink = helperText.find('a'); + const helperTextLinkAttrs = helperTextLink.attributes(); + + expect(helperText.text()).toBe( + 'Changing a Release tag is only supported via Releases API. More information', + ); + expect(helperTextLink.text()).toBe('More information'); + expect(helperTextLinkAttrs.href).toBe(state.updateReleaseApiDocsPath); + expect(helperTextLinkAttrs.rel).toContain('noopener'); + expect(helperTextLinkAttrs.rel).toContain('noreferrer'); + expect(helperTextLinkAttrs.target).toBe('_blank'); + }); + + it('renders the correct release title in the "Release title" field', () => { + expect(wrapper.find('#release-title').element.value).toBe(releaseClone.name); + }); + + it('renders the release notes in the "Release notes" textarea', () => { + expect(wrapper.find('#release-notes').element.value).toBe(releaseClone.description); + }); + + it('renders the "Save changes" button as type="submit"', () => { + expect(wrapper.find('.js-submit-button').attributes('type')).toBe('submit'); + }); + + it('calls updateRelease when the form is submitted', () => { + wrapper.find('form').trigger('submit'); + expect(actions.updateRelease).toHaveBeenCalledTimes(1); + }); + + it('calls navigateToReleasesPage when the "Cancel" button is clicked', () => { + wrapper.find('.js-cancel-button').vm.$emit('click'); + expect(actions.navigateToReleasesPage).toHaveBeenCalledTimes(1); + }); +}); diff --git a/spec/frontend/releases/components/evidence_block_spec.js b/spec/frontend/releases/components/evidence_block_spec.js new file mode 100644 index 00000000000..7b896575965 --- /dev/null +++ b/spec/frontend/releases/components/evidence_block_spec.js @@ -0,0 +1,75 @@ +import { mount } 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/components/evidence_block.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; + +describe('Evidence Block', () => { + let wrapper; + + const factory = (options = {}) => { + wrapper = mount(EvidenceBlock, { + ...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('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'); + + return wrapper.vm.$nextTick().then(() => { + 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('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/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js new file mode 100644 index 00000000000..4125d5c7e74 --- /dev/null +++ b/spec/frontend/releases/components/release_block_footer_spec.js @@ -0,0 +1,162 @@ +import { mount } from '@vue/test-utils'; +import { GlLink } from '@gitlab/ui'; +import { trimText } from 'helpers/text_helper'; +import ReleaseBlockFooter from '~/releases/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: { + timeFormatted() { + return '7 fortnights ago'; + }, + tooltipTitle() { + return 'February 30, 2401'; + }, + }, +})); + +describe('Release block footer', () => { + let wrapper; + let releaseClone; + + const factory = (props = {}) => { + wrapper = mount(ReleaseBlockFooter, { + propsData: { + ...convertObjectPropsToCamelCase(releaseClone), + ...props, + }, + }); + + return wrapper.vm.$nextTick(); + }; + + beforeEach(() => { + releaseClone = JSON.parse(JSON.stringify(release)); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const commitInfoSection = () => wrapper.find('.js-commit-info'); + const commitInfoSectionLink = () => commitInfoSection().find(GlLink); + const tagInfoSection = () => wrapper.find('.js-tag-info'); + const tagInfoSectionLink = () => tagInfoSection().find(GlLink); + const authorDateInfoSection = () => wrapper.find('.js-author-date-info'); + + describe('with all props provided', () => { + beforeEach(() => factory()); + + it('renders the commit icon', () => { + const commitIcon = commitInfoSection().find(Icon); + + expect(commitIcon.exists()).toBe(true); + expect(commitIcon.props('name')).toBe('commit'); + }); + + it('renders the commit SHA with a link', () => { + const commitLink = commitInfoSectionLink(); + + expect(commitLink.exists()).toBe(true); + expect(commitLink.text()).toBe(releaseClone.commit.short_id); + expect(commitLink.attributes('href')).toBe(releaseClone.commit_path); + }); + + it('renders the tag icon', () => { + const commitIcon = tagInfoSection().find(Icon); + + expect(commitIcon.exists()).toBe(true); + expect(commitIcon.props('name')).toBe('tag'); + }); + + it('renders the tag name with a link', () => { + const commitLink = tagInfoSection().find(GlLink); + + expect(commitLink.exists()).toBe(true); + expect(commitLink.text()).toBe(releaseClone.tag_name); + expect(commitLink.attributes('href')).toBe(releaseClone.tag_path); + }); + + it('renders the author and creation time info', () => { + expect(trimText(authorDateInfoSection().text())).toBe( + `Created 7 fortnights ago by ${releaseClone.author.username}`, + ); + }); + + it("renders the author's avatar image", () => { + const avatarImg = authorDateInfoSection().find('img'); + + expect(avatarImg.exists()).toBe(true); + expect(avatarImg.attributes('src')).toBe(releaseClone.author.avatar_url); + }); + + it("renders a link to the author's profile", () => { + const authorLink = authorDateInfoSection().find(GlLink); + + expect(authorLink.exists()).toBe(true); + expect(authorLink.attributes('href')).toBe(releaseClone.author.web_url); + }); + }); + + describe('without any commit info', () => { + beforeEach(() => factory({ commit: undefined })); + + it('does not render any commit info', () => { + expect(commitInfoSection().exists()).toBe(false); + }); + }); + + describe('without a commit URL', () => { + beforeEach(() => factory({ commitPath: undefined })); + + it('renders the commit SHA as plain text (instead of a link)', () => { + expect(commitInfoSectionLink().exists()).toBe(false); + expect(commitInfoSection().text()).toBe(releaseClone.commit.short_id); + }); + }); + + describe('without a tag name', () => { + beforeEach(() => factory({ tagName: undefined })); + + it('does not render any tag info', () => { + expect(tagInfoSection().exists()).toBe(false); + }); + }); + + describe('without a tag URL', () => { + beforeEach(() => factory({ tagPath: undefined })); + + it('renders the tag name as plain text (instead of a link)', () => { + expect(tagInfoSectionLink().exists()).toBe(false); + expect(tagInfoSection().text()).toBe(releaseClone.tag_name); + }); + }); + + describe('without any author info', () => { + beforeEach(() => factory({ author: undefined })); + + it('renders the release date without the author name', () => { + expect(trimText(authorDateInfoSection().text())).toBe('Created 7 fortnights ago'); + }); + }); + + describe('without a released at date', () => { + beforeEach(() => factory({ releasedAt: undefined })); + + it('renders the author name without the release date', () => { + expect(trimText(authorDateInfoSection().text())).toBe( + `Created by ${releaseClone.author.username}`, + ); + }); + }); + + describe('without a release date or author info', () => { + beforeEach(() => factory({ author: undefined, releasedAt: undefined })); + + it('does not render any author or release date info', () => { + expect(authorDateInfoSection().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js new file mode 100644 index 00000000000..157df15ff3c --- /dev/null +++ b/spec/frontend/releases/components/release_block_header_spec.js @@ -0,0 +1,56 @@ +import { shallowMount } from '@vue/test-utils'; +import { cloneDeep, merge } from 'lodash'; +import { GlLink } from '@gitlab/ui'; +import ReleaseBlockHeader from '~/releases/components/release_block_header.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { release as originalRelease } from '../mock_data'; + +describe('Release block header', () => { + let wrapper; + let release; + + const factory = (releaseUpdates = {}) => { + wrapper = shallowMount(ReleaseBlockHeader, { + propsData: { + release: merge({}, release, releaseUpdates), + }, + }); + }; + + beforeEach(() => { + release = convertObjectPropsToCamelCase(cloneDeep(originalRelease), { + ignoreKeyNames: ['_links'], + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const findHeader = () => wrapper.find('h2'); + const findHeaderLink = () => findHeader().find(GlLink); + + describe('when _links.self is provided', () => { + beforeEach(() => { + factory(); + }); + + it('renders the title as a link', () => { + const link = findHeaderLink(); + + expect(link.text()).toBe(release.name); + expect(link.attributes('href')).toBe(release._links.self); + }); + }); + + describe('when _links.self is missing', () => { + beforeEach(() => { + factory({ _links: { self: null } }); + }); + + it('renders the title as text', () => { + expect(findHeader().text()).toBe(release.name); + expect(findHeaderLink().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/releases/components/release_block_milestone_info_spec.js b/spec/frontend/releases/components/release_block_milestone_info_spec.js new file mode 100644 index 00000000000..5a3204a4ce2 --- /dev/null +++ b/spec/frontend/releases/components/release_block_milestone_info_spec.js @@ -0,0 +1,178 @@ +import { mount } from '@vue/test-utils'; +import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui'; +import { trimText } from 'helpers/text_helper'; +import ReleaseBlockMilestoneInfo from '~/releases/components/release_block_milestone_info.vue'; +import { milestones } from '../mock_data'; +import { MAX_MILESTONES_TO_DISPLAY } from '~/releases/constants'; + +describe('Release block milestone info', () => { + let wrapper; + let milestonesClone; + + const factory = milestonesProp => { + wrapper = mount(ReleaseBlockMilestoneInfo, { + propsData: { + milestones: milestonesProp, + }, + }); + + 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('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/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js new file mode 100644 index 00000000000..aba1b8aff41 --- /dev/null +++ b/spec/frontend/releases/components/release_block_spec.js @@ -0,0 +1,296 @@ +import $ from 'jquery'; +import { mount } from '@vue/test-utils'; +import { first } from 'underscore'; +import EvidenceBlock from '~/releases/components/evidence_block.vue'; +import ReleaseBlock from '~/releases/components/release_block.vue'; +import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; +import { release } from '../mock_data'; +import Icon from '~/vue_shared/components/icon.vue'; +import { scrollToElement } from '~/lib/utils/common_utils'; + +let mockLocationHash; +jest.mock('~/lib/utils/url_utility', () => ({ + __esModule: true, + getLocationHash: jest.fn().mockImplementation(() => mockLocationHash), +})); + +jest.mock('~/lib/utils/common_utils', () => ({ + __esModule: true, + scrollToElement: jest.fn(), +})); + +describe('Release block', () => { + let wrapper; + let releaseClone; + + const factory = (releaseProp, featureFlags = {}) => { + wrapper = mount(ReleaseBlock, { + propsData: { + release: releaseProp, + }, + provide: { + glFeatures: { + releaseIssueSummary: true, + ...featureFlags, + }, + }, + }); + + return wrapper.vm.$nextTick(); + }; + + const milestoneListLabel = () => wrapper.find('.js-milestone-list-label'); + const editButton = () => wrapper.find('.js-edit-button'); + + beforeEach(() => { + jest.spyOn($.fn, 'renderGFM'); + releaseClone = JSON.parse(JSON.stringify(release)); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('with default props', () => { + beforeEach(() => factory(release)); + + it("renders the block with an id equal to the release's tag name", () => { + expect(wrapper.attributes().id).toBe('v0.3'); + }); + + it('renders an edit button that links to the "Edit release" page', () => { + expect(editButton().exists()).toBe(true); + expect(editButton().attributes('href')).toBe(release._links.edit_url); + }); + + it('renders release name', () => { + expect(wrapper.text()).toContain(release.name); + }); + + it('renders release description', () => { + expect(wrapper.vm.$refs['gfm-content']).toBeDefined(); + expect($.fn.renderGFM).toHaveBeenCalledTimes(1); + }); + + it('renders release date', () => { + expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormatted(release.released_at)); + }); + + it('renders number of assets provided', () => { + expect(wrapper.find('.js-assets-count').text()).toContain(release.assets.count); + }); + + it('renders dropdown with the sources', () => { + expect(wrapper.findAll('.js-sources-dropdown li').length).toEqual( + release.assets.sources.length, + ); + + expect(wrapper.find('.js-sources-dropdown li a').attributes().href).toEqual( + first(release.assets.sources).url, + ); + + expect(wrapper.find('.js-sources-dropdown li a').text()).toContain( + first(release.assets.sources).format, + ); + }); + + it('renders list with the links provided', () => { + expect(wrapper.findAll('.js-assets-list li').length).toEqual(release.assets.links.length); + + expect(wrapper.find('.js-assets-list li a').attributes().href).toEqual( + first(release.assets.links).url, + ); + + expect(wrapper.find('.js-assets-list li a').text()).toContain( + first(release.assets.links).name, + ); + }); + + it('renders author avatar', () => { + expect(wrapper.find('.user-avatar-link').exists()).toBe(true); + }); + + describe('external label', () => { + it('renders external label when link is external', () => { + expect(wrapper.find('.js-assets-list li a').text()).toContain('external source'); + }); + + it('does not render external label when link is not external', () => { + expect(wrapper.find('.js-assets-list li:nth-child(2) a').text()).not.toContain( + 'external source', + ); + }); + }); + + it('renders the footer', () => { + expect(wrapper.find(ReleaseBlockFooter).exists()).toBe(true); + }); + }); + + it('renders commit sha', () => { + releaseClone.commit_path = '/commit/example'; + + return factory(releaseClone).then(() => { + expect(wrapper.text()).toContain(release.commit.short_id); + + expect(wrapper.find('a[href="/commit/example"]').exists()).toBe(true); + }); + }); + + it('renders tag name', () => { + releaseClone.tag_path = '/tag/example'; + + return factory(releaseClone).then(() => { + expect(wrapper.text()).toContain(release.tag_name); + + expect(wrapper.find('a[href="/tag/example"]').exists()).toBe(true); + }); + }); + + it("does not render an edit button if release._links.edit_url isn't a string", () => { + delete releaseClone._links; + + return factory(releaseClone).then(() => { + expect(editButton().exists()).toBe(false); + }); + }); + + it('does not render the milestone list if no milestones are associated to the release', () => { + delete releaseClone.milestones; + + return factory(releaseClone).then(() => { + expect(milestoneListLabel().exists()).toBe(false); + }); + }); + + it('renders upcoming release badge', () => { + releaseClone.upcoming_release = true; + + return factory(releaseClone).then(() => { + expect(wrapper.text()).toContain('Upcoming Release'); + }); + }); + + it('slugifies the tag_name before setting it as the elements ID', () => { + releaseClone.tag_name = 'a dangerous tag name <script>alert("hello")</script>'; + + return factory(releaseClone).then(() => { + expect(wrapper.attributes().id).toBe('a-dangerous-tag-name-script-alert-hello-script'); + }); + }); + + 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(); + }); + + const hasTargetBlueBackground = () => wrapper.classes('bg-line-target-blue'); + + it('does not attempt to scroll the page if no anchor tag is included in the URL', () => { + mockLocationHash = ''; + return factory(release).then(() => { + expect(scrollToElement).not.toHaveBeenCalled(); + }); + }); + + it("does not attempt to scroll the page if the anchor tag doesn't match the release's tag name", () => { + mockLocationHash = 'v0.4'; + return factory(release).then(() => { + expect(scrollToElement).not.toHaveBeenCalled(); + }); + }); + + it("attempts to scroll itself into view if the anchor tag matches the release's tag name", () => { + mockLocationHash = release.tag_name; + return factory(release).then(() => { + expect(scrollToElement).toHaveBeenCalledTimes(1); + + expect(scrollToElement).toHaveBeenCalledWith(wrapper.element); + }); + }); + + it('renders with a light blue background if it is the target of the anchor', () => { + mockLocationHash = release.tag_name; + + return factory(release).then(() => { + expect(hasTargetBlueBackground()).toBe(true); + }); + }); + + it('does not render with a light blue background if it is not the target of the anchor', () => { + mockLocationHash = ''; + + return factory(release).then(() => { + expect(hasTargetBlueBackground()).toBe(false); + }); + }); + }); + + 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('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'); + }); + }); + }); +}); |