Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-15 00:09:08 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-15 00:09:08 +0300
commit866ca4e49ff74ffadf8e6f6ff663a168489c2aba (patch)
treecc3135b1bae11dbd1cb3a30cb547473ad89a5551 /spec/frontend/releases/components
parent26a50872e9da9509c52c70f74dc21698fec906db (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/releases/components')
-rw-r--r--spec/frontend/releases/components/app_edit_spec.js97
-rw-r--r--spec/frontend/releases/components/evidence_block_spec.js75
-rw-r--r--spec/frontend/releases/components/release_block_footer_spec.js162
-rw-r--r--spec/frontend/releases/components/release_block_header_spec.js56
-rw-r--r--spec/frontend/releases/components/release_block_milestone_info_spec.js178
-rw-r--r--spec/frontend/releases/components/release_block_spec.js296
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');
+ });
+ });
+ });
+});