Skip to content
Snippets Groups Projects
Commit d80f3cd7 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent be81c157
No related branches found
No related tags found
No related merge requests found
Showing
with 453 additions and 168 deletions
# frozen_string_literal: true
require 'spec_helper'
describe 'User views Release', :js do
let(:project) { create(:project, :repository) }
let(:release) { create(:release, project: project, name: 'The first release' ) }
let(:user) { create(:user) }
before do
project.add_developer(user)
gitlab_sign_in(user)
visit project_release_path(project, release)
end
it 'renders the breadcrumbs' do
within('.breadcrumbs') do
expect(page).to have_content("#{project.creator.name} #{project.name} Releases #{release.name}")
expect(page).to have_link(project.creator.name, href: user_path(project.creator))
expect(page).to have_link(project.name, href: project_path(project))
expect(page).to have_link('Releases', href: project_releases_path(project))
expect(page).to have_link(release.name, href: project_release_path(project, release))
end
end
it 'renders the release details' do
within('.release-block') do
expect(page).to have_content(release.name)
expect(page).to have_content(release.tag)
expect(page).to have_content(release.commit.short_id)
expect(page).to have_content(release.description)
end
end
end
import { shallowMount } from '@vue/test-utils';
import Popover from '~/blob/suggest_gitlab_ci_yml/components/popover.vue';
import Cookies from 'js-cookie';
const popoverTarget = 'gitlab-ci-yml-selector';
const dismissKey = 'suggest_gitlab_ci_yml_99';
describe('Suggest gitlab-ci.yml Popover', () => {
let wrapper;
function createWrapper() {
wrapper = shallowMount(Popover, {
propsData: {
target: popoverTarget,
cssClass: 'js-class',
dismissKey,
},
});
}
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when no dismiss cookie is set', () => {
beforeEach(() => {
createWrapper();
});
it('sets popoverDismissed to false', () => {
expect(wrapper.vm.popoverDismissed).toEqual(false);
});
});
describe('when the dismiss cookie is set', () => {
beforeEach(() => {
Cookies.set(dismissKey, true);
createWrapper();
});
it('sets popoverDismissed to true', () => {
expect(wrapper.vm.popoverDismissed).toEqual(true);
});
});
});
Loading
Loading
@@ -6,56 +6,75 @@ jest.mock('~/lib/utils/icon_utils', () => ({
getSvgIconPathContent: jest.fn().mockResolvedValue('mockSvgPathContent'),
}));
 
const yAxisName = 'Y-axis mock name';
const yAxisFormat = 'bytes';
const yAxisPrecistion = 3;
const dataValues = [
[1495700554.925, '8.0390625'],
[1495700614.925, '8.0390625'],
[1495700674.925, '8.0390625'],
];
describe('Column component', () => {
let columnChart;
let wrapper;
const findChart = () => wrapper.find(GlColumnChart);
const chartProps = prop => findChart().props(prop);
 
beforeEach(() => {
columnChart = shallowMount(ColumnChart, {
wrapper = shallowMount(ColumnChart, {
propsData: {
graphData: {
yAxis: {
name: yAxisName,
format: yAxisFormat,
precision: yAxisPrecistion,
},
metrics: [
{
x_label: 'Time',
y_label: 'Usage',
result: [
{
metric: {},
values: [
[1495700554.925, '8.0390625'],
[1495700614.925, '8.0390625'],
[1495700674.925, '8.0390625'],
],
values: dataValues,
},
],
},
],
},
containerWidth: 100,
},
});
});
 
afterEach(() => {
columnChart.destroy();
wrapper.destroy();
});
 
describe('wrapped components', () => {
describe('GitLab UI column chart', () => {
let glColumnChart;
it('is a Vue instance', () => {
expect(findChart().isVueInstance()).toBe(true);
});
 
beforeEach(() => {
glColumnChart = columnChart.find(GlColumnChart);
it('receives data properties needed for proper chart render', () => {
expect(chartProps('data').values).toEqual(dataValues);
});
 
it('is a Vue instance', () => {
expect(glColumnChart.isVueInstance()).toBe(true);
it('passes the y axis name correctly', () => {
expect(chartProps('yAxisTitle')).toBe(yAxisName);
});
 
it('receives data properties needed for proper chart render', () => {
const props = glColumnChart.props();
it('passes the y axis configuration correctly', () => {
expect(chartProps('option').yAxis).toMatchObject({
name: yAxisName,
axisLabel: {
formatter: expect.any(Function),
},
scale: false,
});
});
 
expect(props.data).toBe(columnChart.vm.chartData);
expect(props.option).toBe(columnChart.vm.chartOptions);
it('passes a dataZoom configuration', () => {
expect(chartProps('option').dataZoom).toBeDefined();
});
});
});
Loading
Loading
Loading
Loading
@@ -544,6 +544,12 @@ export const dashboardGitResponse = [
...customDashboardsData,
];
 
export const mockDashboardsErrorResponse = {
all_dashboards: customDashboardsData,
message: "Each 'panel_group' must define an array :panels",
status: 'error',
};
export const graphDataPrometheusQuery = {
title: 'Super Chart A2',
type: 'single-stat',
Loading
Loading
Loading
Loading
@@ -30,6 +30,7 @@ import {
metricsDashboardResponse,
metricsDashboardViewModel,
dashboardGitResponse,
mockDashboardsErrorResponse,
} from '../mock_data';
 
jest.mock('~/flash');
Loading
Loading
@@ -257,9 +258,11 @@ describe('Monitoring store actions', () => {
describe('fetchDashboard', () => {
let dispatch;
let state;
let commit;
const response = metricsDashboardResponse;
beforeEach(() => {
dispatch = jest.fn();
commit = jest.fn();
state = storeState();
state.dashboardEndpoint = '/dashboard';
});
Loading
Loading
@@ -270,6 +273,7 @@ describe('Monitoring store actions', () => {
fetchDashboard(
{
state,
commit,
dispatch,
},
params,
Loading
Loading
@@ -287,19 +291,21 @@ describe('Monitoring store actions', () => {
 
describe('on failure', () => {
let result;
let errorResponse;
beforeEach(() => {
const params = {};
result = () => {
mock.onGet(state.dashboardEndpoint).replyOnce(500, errorResponse);
return fetchDashboard({ state, dispatch }, params);
mock.onGet(state.dashboardEndpoint).replyOnce(500, mockDashboardsErrorResponse);
return fetchDashboard({ state, commit, dispatch }, params);
};
});
 
it('dispatches a failure action', done => {
errorResponse = {};
result()
.then(() => {
expect(commit).toHaveBeenCalledWith(
types.SET_ALL_DASHBOARDS,
mockDashboardsErrorResponse.all_dashboards,
);
expect(dispatch).toHaveBeenCalledWith(
'receiveMetricsDashboardFailure',
new Error('Request failed with status code 500'),
Loading
Loading
@@ -311,15 +317,15 @@ describe('Monitoring store actions', () => {
});
 
it('dispatches a failure action when a message is returned', done => {
const message = 'Something went wrong with Prometheus!';
errorResponse = { message };
result()
.then(() => {
expect(dispatch).toHaveBeenCalledWith(
'receiveMetricsDashboardFailure',
new Error('Request failed with status code 500'),
);
expect(createFlash).toHaveBeenCalledWith(expect.stringContaining(message));
expect(createFlash).toHaveBeenCalledWith(
expect.stringContaining(mockDashboardsErrorResponse.message),
);
done();
})
.catch(done.fail);
Loading
Loading
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';
import { release as originalRelease } from '../mock_data';
import * as commonUtils from '~/lib/utils/common_utils';
import { BACK_URL_PARAM } from '~/releases/constants';
 
describe('Release edit component', () => {
let wrapper;
let releaseClone;
let release;
let actions;
let state;
 
beforeEach(() => {
gon.api_version = 'v4';
releaseClone = convertObjectPropsToCamelCase(release, { deep: true });
const factory = () => {
state = {
release: releaseClone,
release,
markdownDocsPath: 'path/to/markdown/docs',
updateReleaseApiDocsPath: 'path/to/update/release/api/docs',
releasesPagePath: 'path/to/releases/page',
};
 
actions = {
fetchRelease: jest.fn(),
updateRelease: jest.fn(),
navigateToReleasesPage: jest.fn(),
};
 
const store = new Vuex.Store({
Loading
Loading
@@ -40,58 +37,99 @@ describe('Release edit component', () => {
wrapper = mount(ReleaseEditApp, {
store,
});
};
 
return wrapper.vm.$nextTick();
});
beforeEach(() => {
gon.api_version = 'v4';
 
it('calls fetchRelease when the component is created', () => {
expect(actions.fetchRelease).toHaveBeenCalledTimes(1);
release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true });
});
 
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.',
);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
 
it('renders the correct tag name in the "Tag name" field', () => {
expect(wrapper.find('#git-ref').element.value).toBe(releaseClone.tagName);
});
describe(`basic functionality tests: all tests unrelated to the "${BACK_URL_PARAM}" parameter`, () => {
beforeEach(() => {
factory();
});
 
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('calls fetchRelease when the component is created', () => {
expect(actions.fetchRelease).toHaveBeenCalledTimes(1);
});
 
it('renders the correct release title in the "Release title" field', () => {
expect(wrapper.find('#release-title').element.value).toBe(releaseClone.name);
});
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 release notes in the "Release notes" textarea', () => {
expect(wrapper.find('#release-notes').element.value).toBe(releaseClone.description);
});
it('renders the correct tag name in the "Tag name" field', () => {
expect(wrapper.find('#git-ref').element.value).toBe(release.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).toEqual(
expect.objectContaining({
href: state.updateReleaseApiDocsPath,
rel: 'noopener noreferrer',
target: '_blank',
}),
);
});
it('renders the correct release title in the "Release title" field', () => {
expect(wrapper.find('#release-title').element.value).toBe(release.name);
});
it('renders the release notes in the "Release notes" textarea', () => {
expect(wrapper.find('#release-notes').element.value).toBe(release.description);
});
it('renders the "Save changes" button as type="submit"', () => {
expect(wrapper.find('.js-submit-button').attributes('type')).toBe('submit');
});
 
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 updateRelease when the form is submitted', () => {
wrapper.find('form').trigger('submit');
expect(actions.updateRelease).toHaveBeenCalledTimes(1);
describe(`when the URL does not contain a "${BACK_URL_PARAM}" parameter`, () => {
beforeEach(() => {
factory();
});
it(`renders a "Cancel" button with an href pointing to "${BACK_URL_PARAM}"`, () => {
const cancelButton = wrapper.find('.js-cancel-button');
expect(cancelButton.attributes().href).toBe(state.releasesPagePath);
});
});
 
it('calls navigateToReleasesPage when the "Cancel" button is clicked', () => {
wrapper.find('.js-cancel-button').vm.$emit('click');
expect(actions.navigateToReleasesPage).toHaveBeenCalledTimes(1);
describe(`when the URL contains a "${BACK_URL_PARAM}" parameter`, () => {
const backUrl = 'https://example.gitlab.com/back/url';
beforeEach(() => {
commonUtils.getParameterByName = jest
.fn()
.mockImplementation(paramToGet => ({ [BACK_URL_PARAM]: backUrl }[paramToGet]));
factory();
});
it('renders a "Cancel" button with an href pointing to the main Releases page', () => {
const cancelButton = wrapper.find('.js-cancel-button');
expect(cancelButton.attributes().href).toBe(backUrl);
});
});
});
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import ReleaseShowApp from '~/releases/components/app_show.vue';
import { release as originalRelease } from '../mock_data';
import { GlSkeletonLoading } from '@gitlab/ui';
import ReleaseBlock from '~/releases/components/release_block.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
describe('Release show component', () => {
let wrapper;
let release;
let actions;
beforeEach(() => {
release = convertObjectPropsToCamelCase(originalRelease);
});
const factory = state => {
actions = {
fetchRelease: jest.fn(),
};
const store = new Vuex.Store({
modules: {
detail: {
namespaced: true,
actions,
state,
},
},
});
wrapper = shallowMount(ReleaseShowApp, { store });
};
const findLoadingSkeleton = () => wrapper.find(GlSkeletonLoading);
const findReleaseBlock = () => wrapper.find(ReleaseBlock);
it('calls fetchRelease when the component is created', () => {
factory({ release });
expect(actions.fetchRelease).toHaveBeenCalledTimes(1);
});
it('shows a loading skeleton and hides the release block while the API call is in progress', () => {
factory({ isFetchingRelease: true });
expect(findLoadingSkeleton().exists()).toBe(true);
expect(findReleaseBlock().exists()).toBe(false);
});
it('hides the loading skeleton and shows the release block when the API call finishes successfully', () => {
factory({ isFetchingRelease: false });
expect(findLoadingSkeleton().exists()).toBe(false);
expect(findReleaseBlock().exists()).toBe(true);
});
it('hides both the loading skeleton and the release block when the API call fails', () => {
factory({ fetchError: new Error('Uh oh') });
expect(findLoadingSkeleton().exists()).toBe(false);
expect(findReleaseBlock().exists()).toBe(false);
});
});
Loading
Loading
@@ -4,6 +4,7 @@ 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';
import { BACK_URL_PARAM } from '~/releases/constants';
 
describe('Release block header', () => {
let wrapper;
Loading
Loading
@@ -27,6 +28,7 @@ describe('Release block header', () => {
 
const findHeader = () => wrapper.find('h2');
const findHeaderLink = () => findHeader().find(GlLink);
const findEditButton = () => wrapper.find('.js-edit-button');
 
describe('when _links.self is provided', () => {
beforeEach(() => {
Loading
Loading
@@ -51,4 +53,39 @@ describe('Release block header', () => {
expect(findHeaderLink().exists()).toBe(false);
});
});
describe('when _links.edit_url is provided', () => {
const currentUrl = 'https://example.gitlab.com/path';
beforeEach(() => {
Object.defineProperty(window, 'location', {
writable: true,
value: {
href: currentUrl,
},
});
factory();
});
it('renders an edit button', () => {
expect(findEditButton().exists()).toBe(true);
});
it('renders the edit button with the correct href', () => {
const expectedQueryParam = `${BACK_URL_PARAM}=${encodeURIComponent(currentUrl)}`;
const expectedUrl = `${release._links.editUrl}?${expectedQueryParam}`;
expect(findEditButton().attributes().href).toBe(expectedUrl);
});
});
describe('when _links.edit is missing', () => {
beforeEach(() => {
factory({ _links: { editUrl: null } });
});
it('does not render an edit button', () => {
expect(findEditButton().exists()).toBe(false);
});
});
});
Loading
Loading
@@ -7,20 +7,9 @@ import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { release as originalRelease } from '../mock_data';
import Icon from '~/vue_shared/components/icon.vue';
import { scrollToElement } from '~/lib/utils/common_utils';
const { convertObjectPropsToCamelCase } = jest.requireActual('~/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(),
}));
import * as commonUtils from '~/lib/utils/common_utils';
import { BACK_URL_PARAM } from '~/releases/constants';
import * as urlUtility from '~/lib/utils/url_utility';
 
describe('Release block', () => {
let wrapper;
Loading
Loading
@@ -47,7 +36,7 @@ describe('Release block', () => {
 
beforeEach(() => {
jest.spyOn($.fn, 'renderGFM');
release = convertObjectPropsToCamelCase(originalRelease, { deep: true });
release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true });
});
 
afterEach(() => {
Loading
Loading
@@ -61,9 +50,11 @@ describe('Release block', () => {
expect(wrapper.attributes().id).toBe('v0.3');
});
 
it('renders an edit button that links to the "Edit release" page', () => {
it(`renders an edit button that links to the "Edit release" page with a "${BACK_URL_PARAM}" parameter`, () => {
expect(editButton().exists()).toBe(true);
expect(editButton().attributes('href')).toBe(release._links.editUrl);
expect(editButton().attributes('href')).toBe(
`${release._links.editUrl}?${BACK_URL_PARAM}=${encodeURIComponent(window.location.href)}`,
);
});
 
it('renders release name', () => {
Loading
Loading
@@ -150,14 +141,6 @@ describe('Release block', () => {
});
});
 
it("does not render an edit button if release._links.editUrl isn't a string", () => {
delete release._links;
return factory(release).then(() => {
expect(editButton().exists()).toBe(false);
});
});
it('does not render the milestone list if no milestones are associated to the release', () => {
delete release.milestones;
 
Loading
Loading
@@ -203,37 +186,40 @@ describe('Release block', () => {
});
 
describe('anchor scrolling', () => {
let locationHash;
beforeEach(() => {
scrollToElement.mockClear();
commonUtils.scrollToElement = jest.fn();
urlUtility.getLocationHash = jest.fn().mockImplementation(() => locationHash);
});
 
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 = '';
locationHash = '';
return factory(release).then(() => {
expect(scrollToElement).not.toHaveBeenCalled();
expect(commonUtils.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';
locationHash = 'v0.4';
return factory(release).then(() => {
expect(scrollToElement).not.toHaveBeenCalled();
expect(commonUtils.scrollToElement).not.toHaveBeenCalled();
});
});
 
it("attempts to scroll itself into view if the anchor tag matches the release's tag name", () => {
mockLocationHash = release.tagName;
locationHash = release.tagName;
return factory(release).then(() => {
expect(scrollToElement).toHaveBeenCalledTimes(1);
expect(commonUtils.scrollToElement).toHaveBeenCalledTimes(1);
 
expect(scrollToElement).toHaveBeenCalledWith(wrapper.element);
expect(commonUtils.scrollToElement).toHaveBeenCalledWith(wrapper.element);
});
});
 
it('renders with a light blue background if it is the target of the anchor', () => {
mockLocationHash = release.tagName;
locationHash = release.tagName;
 
return factory(release).then(() => {
expect(hasTargetBlueBackground()).toBe(true);
Loading
Loading
@@ -241,7 +227,7 @@ describe('Release block', () => {
});
 
it('does not render with a light blue background if it is not the target of the anchor', () => {
mockLocationHash = '';
locationHash = '';
 
return factory(release).then(() => {
expect(hasTargetBlueBackground()).toBe(false);
Loading
Loading
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { cloneDeep, merge } from 'lodash';
import * as actions from '~/releases/stores/modules/detail/actions';
import * as types from '~/releases/stores/modules/detail/mutation_types';
import { release } from '../../../mock_data';
import state from '~/releases/stores/modules/detail/state';
import { release as originalRelease } from '../../../mock_data';
import createState from '~/releases/stores/modules/detail/state';
import createFlash from '~/flash';
import { redirectTo } from '~/lib/utils/url_utility';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { redirectTo } from '~/lib/utils/url_utility';
 
jest.mock('~/flash', () => jest.fn());
 
Loading
Loading
@@ -17,14 +18,14 @@ jest.mock('~/lib/utils/url_utility', () => ({
}));
 
describe('Release detail actions', () => {
let stateClone;
let releaseClone;
let state;
let release;
let mock;
let error;
 
beforeEach(() => {
stateClone = state();
releaseClone = JSON.parse(JSON.stringify(release));
state = createState();
release = cloneDeep(originalRelease);
mock = new MockAdapter(axios);
gon.api_version = 'v4';
error = { message: 'An error occurred' };
Loading
Loading
@@ -39,7 +40,7 @@ describe('Release detail actions', () => {
it(`commits ${types.SET_INITIAL_STATE} with the provided object`, () => {
const initialState = {};
 
return testAction(actions.setInitialState, initialState, stateClone, [
return testAction(actions.setInitialState, initialState, state, [
{ type: types.SET_INITIAL_STATE, payload: initialState },
]);
});
Loading
Loading
@@ -47,19 +48,19 @@ describe('Release detail actions', () => {
 
describe('requestRelease', () => {
it(`commits ${types.REQUEST_RELEASE}`, () =>
testAction(actions.requestRelease, undefined, stateClone, [{ type: types.REQUEST_RELEASE }]));
testAction(actions.requestRelease, undefined, state, [{ type: types.REQUEST_RELEASE }]));
});
 
describe('receiveReleaseSuccess', () => {
it(`commits ${types.RECEIVE_RELEASE_SUCCESS}`, () =>
testAction(actions.receiveReleaseSuccess, releaseClone, stateClone, [
{ type: types.RECEIVE_RELEASE_SUCCESS, payload: releaseClone },
testAction(actions.receiveReleaseSuccess, release, state, [
{ type: types.RECEIVE_RELEASE_SUCCESS, payload: release },
]));
});
 
describe('receiveReleaseError', () => {
it(`commits ${types.RECEIVE_RELEASE_ERROR}`, () =>
testAction(actions.receiveReleaseError, error, stateClone, [
testAction(actions.receiveReleaseError, error, state, [
{ type: types.RECEIVE_RELEASE_ERROR, payload: error },
]));
 
Loading
Loading
@@ -77,24 +78,24 @@ describe('Release detail actions', () => {
let getReleaseUrl;
 
beforeEach(() => {
stateClone.projectId = '18';
stateClone.tagName = 'v1.3';
getReleaseUrl = `/api/v4/projects/${stateClone.projectId}/releases/${stateClone.tagName}`;
state.projectId = '18';
state.tagName = 'v1.3';
getReleaseUrl = `/api/v4/projects/${state.projectId}/releases/${state.tagName}`;
});
 
it(`dispatches requestRelease and receiveReleaseSuccess with the camel-case'd release object`, () => {
mock.onGet(getReleaseUrl).replyOnce(200, releaseClone);
mock.onGet(getReleaseUrl).replyOnce(200, release);
 
return testAction(
actions.fetchRelease,
undefined,
stateClone,
state,
[],
[
{ type: 'requestRelease' },
{
type: 'receiveReleaseSuccess',
payload: convertObjectPropsToCamelCase(releaseClone, { deep: true }),
payload: convertObjectPropsToCamelCase(release, { deep: true }),
},
],
);
Loading
Loading
@@ -106,7 +107,7 @@ describe('Release detail actions', () => {
return testAction(
actions.fetchRelease,
undefined,
stateClone,
state,
[],
[{ type: 'requestRelease' }, { type: 'receiveReleaseError', payload: expect.anything() }],
);
Loading
Loading
@@ -116,7 +117,7 @@ describe('Release detail actions', () => {
describe('updateReleaseTitle', () => {
it(`commits ${types.UPDATE_RELEASE_TITLE} with the updated release title`, () => {
const newTitle = 'The new release title';
return testAction(actions.updateReleaseTitle, newTitle, stateClone, [
return testAction(actions.updateReleaseTitle, newTitle, state, [
{ type: types.UPDATE_RELEASE_TITLE, payload: newTitle },
]);
});
Loading
Loading
@@ -125,7 +126,7 @@ describe('Release detail actions', () => {
describe('updateReleaseNotes', () => {
it(`commits ${types.UPDATE_RELEASE_NOTES} with the updated release notes`, () => {
const newReleaseNotes = 'The new release notes';
return testAction(actions.updateReleaseNotes, newReleaseNotes, stateClone, [
return testAction(actions.updateReleaseNotes, newReleaseNotes, state, [
{ type: types.UPDATE_RELEASE_NOTES, payload: newReleaseNotes },
]);
});
Loading
Loading
@@ -133,25 +134,40 @@ describe('Release detail actions', () => {
 
describe('requestUpdateRelease', () => {
it(`commits ${types.REQUEST_UPDATE_RELEASE}`, () =>
testAction(actions.requestUpdateRelease, undefined, stateClone, [
testAction(actions.requestUpdateRelease, undefined, state, [
{ type: types.REQUEST_UPDATE_RELEASE },
]));
});
 
describe('receiveUpdateReleaseSuccess', () => {
it(`commits ${types.RECEIVE_UPDATE_RELEASE_SUCCESS}`, () =>
testAction(
actions.receiveUpdateReleaseSuccess,
undefined,
stateClone,
[{ type: types.RECEIVE_UPDATE_RELEASE_SUCCESS }],
[{ type: 'navigateToReleasesPage' }],
));
testAction(actions.receiveUpdateReleaseSuccess, undefined, { ...state, featureFlags: {} }, [
{ type: types.RECEIVE_UPDATE_RELEASE_SUCCESS },
]));
describe('when the releaseShowPage feature flag is enabled', () => {
const rootState = { featureFlags: { releaseShowPage: true } };
const updatedState = merge({}, state, {
releasesPagePath: 'path/to/releases/page',
release: {
_links: {
self: 'path/to/self',
},
},
});
actions.receiveUpdateReleaseSuccess({ commit: jest.fn(), state: updatedState, rootState });
expect(redirectTo).toHaveBeenCalledTimes(1);
expect(redirectTo).toHaveBeenCalledWith(updatedState.release._links.self);
});
describe('when the releaseShowPage feature flag is disabled', () => {});
});
 
describe('receiveUpdateReleaseError', () => {
it(`commits ${types.RECEIVE_UPDATE_RELEASE_ERROR}`, () =>
testAction(actions.receiveUpdateReleaseError, error, stateClone, [
testAction(actions.receiveUpdateReleaseError, error, state, [
{ type: types.RECEIVE_UPDATE_RELEASE_ERROR, payload: error },
]));
 
Loading
Loading
@@ -169,10 +185,10 @@ describe('Release detail actions', () => {
let getReleaseUrl;
 
beforeEach(() => {
stateClone.release = releaseClone;
stateClone.projectId = '18';
stateClone.tagName = 'v1.3';
getReleaseUrl = `/api/v4/projects/${stateClone.projectId}/releases/${stateClone.tagName}`;
state.release = release;
state.projectId = '18';
state.tagName = 'v1.3';
getReleaseUrl = `/api/v4/projects/${state.projectId}/releases/${state.tagName}`;
});
 
it(`dispatches requestUpdateRelease and receiveUpdateReleaseSuccess`, () => {
Loading
Loading
@@ -181,7 +197,7 @@ describe('Release detail actions', () => {
return testAction(
actions.updateRelease,
undefined,
stateClone,
state,
[],
[{ type: 'requestUpdateRelease' }, { type: 'receiveUpdateReleaseSuccess' }],
);
Loading
Loading
@@ -193,7 +209,7 @@ describe('Release detail actions', () => {
return testAction(
actions.updateRelease,
undefined,
stateClone,
state,
[],
[
{ type: 'requestUpdateRelease' },
Loading
Loading
@@ -202,16 +218,4 @@ describe('Release detail actions', () => {
);
});
});
describe('navigateToReleasesPage', () => {
it(`calls redirectTo() with the URL to the releases page`, () => {
const releasesPagePath = 'path/to/releases/page';
stateClone.releasesPagePath = releasesPagePath;
actions.navigateToReleasesPage({ state: stateClone });
expect(redirectTo).toHaveBeenCalledTimes(1);
expect(redirectTo).toHaveBeenCalledWith(releasesPagePath);
});
});
});
Loading
Loading
@@ -531,8 +531,10 @@ describe MarkupHelper do
 
it 'preserves style attribute for a label that can be accessed by current_user' do
project = create(:project, :public)
label = create_and_format_label(project)
 
expect(create_and_format_label(project)).to match(/span class=.*style=.*/)
expect(label).to match(/span class=.*style=.*/)
expect(label).to include('data-html="true"')
end
 
it 'does not style a label that can not be accessed by current_user' do
Loading
Loading
@@ -544,6 +546,15 @@ describe MarkupHelper do
end
end
 
it 'keeps whitelisted tags' do
html = '<a><i></i></a> <strong>strong</strong><em>em</em><b>b</b>'
object = create_object(html)
result = first_line_in_markdown(object, attribute, 100, project: project)
expect(result).to include(html)
end
it 'truncates Markdown properly' do
object = create_object("@#{user.username}, can you look at this?\nHello world\n")
actual = first_line_in_markdown(object, attribute, 100, project: project)
Loading
Loading
Loading
Loading
@@ -27,7 +27,7 @@ describe('Releases App ', () => {
};
 
beforeEach(() => {
store = createStore({ list: listModule });
store = createStore({ modules: { list: listModule } });
releasesPagination = _.range(21).map(index => ({
...convertObjectPropsToCamelCase(release, { deep: true }),
tagName: `${index}.00`,
Loading
Loading
Loading
Loading
@@ -425,16 +425,16 @@ describe Issue do
let(:issue) { create(:issue, title: 'testing-issue') }
 
it 'starts with the issue iid' do
expect(issue.to_branch_name).to match /\A#{issue.iid}-[A-Za-z\-]+\z/
expect(issue.to_branch_name).to match(/\A#{issue.iid}-[A-Za-z\-]+\z/)
end
 
it "contains the issue title if not confidential" do
expect(issue.to_branch_name).to match /testing-issue\z/
expect(issue.to_branch_name).to match(/testing-issue\z/)
end
 
it "does not contain the issue title if confidential" do
issue = create(:issue, title: 'testing-issue', confidential: true)
expect(issue.to_branch_name).to match /confidential-issue\z/
expect(issue.to_branch_name).to match(/confidential-issue\z/)
end
 
context 'issue title longer than 100 characters' do
Loading
Loading
@@ -932,4 +932,33 @@ describe Issue do
end
 
it_behaves_like 'versioned description'
describe "#previous_updated_at" do
let_it_be(:updated_at) { Time.new(2012, 01, 06) }
let_it_be(:issue) { create(:issue, updated_at: updated_at) }
it 'returns updated_at value if updated_at did not change at all' do
allow(issue).to receive(:previous_changes).and_return({})
expect(issue.previous_updated_at).to eq(updated_at)
end
it 'returns updated_at value if `previous_changes` has nil value for `updated_at`' do
allow(issue).to receive(:previous_changes).and_return({ 'updated_at' => nil })
expect(issue.previous_updated_at).to eq(updated_at)
end
it 'returns updated_at value if previous updated_at value is not present' do
allow(issue).to receive(:previous_changes).and_return({ 'updated_at' => [nil, Time.new(2013, 02, 06)] })
expect(issue.previous_updated_at).to eq(updated_at)
end
it 'returns previous updated_at when present' do
allow(issue).to receive(:previous_changes).and_return({ 'updated_at' => [Time.new(2013, 02, 06), Time.new(2013, 03, 06)] })
expect(issue.previous_updated_at).to eq(Time.new(2013, 02, 06))
end
end
end
Loading
Loading
@@ -67,7 +67,7 @@ RSpec.describe ResourceWeightEvent, type: :model do
 
it 'returns the expected id' do
allow(Digest::SHA1).to receive(:hexdigest)
.with("ResourceWeightEvent-2019-12-30 00:00:00 UTC-#{user1.id}")
.with("ResourceWeightEvent-#{event.id}-#{user1.id}")
.and_return('73d167c478')
 
expect(event.discussion_id).to eq('73d167c478')
Loading
Loading
Loading
Loading
@@ -26,7 +26,7 @@ module FilteredSearchHelpers
# Select a label clicking in the search dropdown instead
# of entering label names on the input.
def select_label_on_dropdown(label_title)
input_filtered_search("label=", submit: false)
input_filtered_search("label:=", submit: false)
 
within('#js-dropdown-label') do
wait_for_requests
Loading
Loading
@@ -71,7 +71,7 @@ module FilteredSearchHelpers
end
 
def init_label_search
filtered_search.set('label=')
filtered_search.set('label:=')
# This ensures the dropdown is shown
expect(find('#js-dropdown-label')).not_to have_css('.filter-dropdown-loading')
end
Loading
Loading
Loading
Loading
@@ -13,7 +13,7 @@ RSpec.shared_examples 'issuable user dropdown behaviors' do
it 'only includes members of the project/group' do
visit issuables_path
 
filtered_search.set("#{dropdown}=")
filtered_search.set("#{dropdown}:=")
 
expect(find("#js-dropdown-#{dropdown} .filter-dropdown")).to have_content(user_in_dropdown.name)
expect(find("#js-dropdown-#{dropdown} .filter-dropdown")).not_to have_content(user_not_in_dropdown.name)
Loading
Loading
Loading
Loading
@@ -11453,6 +11453,11 @@ underscore@~1.8.3:
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=
 
unfetch@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db"
integrity sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==
unherit@^1.0.4:
version "1.1.1"
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c"
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment