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

Add latest changes from gitlab-org/gitlab@master

parent 232655bf
No related branches found
No related tags found
No related merge requests found
Showing
with 967 additions and 108 deletions
Loading
Loading
@@ -25,7 +25,9 @@
"items": {
"type": "string"
}
},
"token": {
"type": "string"
}
},
"additionalProperties": false
}
}
\ No newline at end of file
{
"type": "object",
"required": [],
"properties": {
"name": { "type": "string" },
"precision": { "type": "number" },
"format": { "type": "string" }
},
"additionalProperties": false
}
Loading
Loading
@@ -9,6 +9,7 @@
"title": { "type": "string" },
"type": { "type": "string" },
"y_label": { "type": "string" },
"y_axis": { "$ref": "axis.json" },
"weight": { "type": "number" },
"metrics": {
"type": "array",
Loading
Loading
Loading
Loading
@@ -3,6 +3,8 @@ import axios from '~/lib/utils/axios_utils';
import BalsamiqViewer from '~/blob/balsamiq/balsamiq_viewer';
import ClassSpecHelper from '../../helpers/class_spec_helper';
 
jest.mock('sql.js');
describe('BalsamiqViewer', () => {
const mockArrayBuffer = new ArrayBuffer(10);
let balsamiqViewer;
Loading
Loading
@@ -34,22 +36,22 @@ describe('BalsamiqViewer', () => {
});
 
it('should call `axios.get` on `endpoint` param with responseType set to `arraybuffer', () => {
spyOn(axios, 'get').and.returnValue(requestSuccess);
spyOn(bv, 'renderFile').and.stub();
jest.spyOn(axios, 'get').mockReturnValue(requestSuccess);
jest.spyOn(bv, 'renderFile').mockReturnValue();
 
bv.loadFile(endpoint);
 
expect(axios.get).toHaveBeenCalledWith(
endpoint,
jasmine.objectContaining({
expect.objectContaining({
responseType: 'arraybuffer',
}),
);
});
 
it('should call `renderFile` on request success', done => {
spyOn(axios, 'get').and.returnValue(requestSuccess);
spyOn(bv, 'renderFile').and.callFake(() => {});
jest.spyOn(axios, 'get').mockReturnValue(requestSuccess);
jest.spyOn(bv, 'renderFile').mockImplementation(() => {});
 
bv.loadFile(endpoint)
.then(() => {
Loading
Loading
@@ -60,8 +62,8 @@ describe('BalsamiqViewer', () => {
});
 
it('should not call `renderFile` on request failure', done => {
spyOn(axios, 'get').and.returnValue(Promise.reject());
spyOn(bv, 'renderFile');
jest.spyOn(axios, 'get').mockReturnValue(Promise.reject());
jest.spyOn(bv, 'renderFile').mockImplementation(() => {});
 
bv.loadFile(endpoint)
.then(() => {
Loading
Loading
@@ -80,19 +82,21 @@ describe('BalsamiqViewer', () => {
let previews;
 
beforeEach(() => {
viewer = jasmine.createSpyObj('viewer', ['appendChild']);
viewer = {
appendChild: jest.fn(),
};
previews = [document.createElement('ul'), document.createElement('ul')];
 
balsamiqViewer = jasmine.createSpyObj('balsamiqViewer', [
'initDatabase',
'getPreviews',
'renderPreview',
]);
balsamiqViewer = {
initDatabase: jest.fn(),
getPreviews: jest.fn(),
renderPreview: jest.fn(),
};
balsamiqViewer.viewer = viewer;
 
balsamiqViewer.getPreviews.and.returnValue(previews);
balsamiqViewer.renderPreview.and.callFake(preview => preview);
viewer.appendChild.and.callFake(containerElement => {
balsamiqViewer.getPreviews.mockReturnValue(previews);
balsamiqViewer.renderPreview.mockImplementation(preview => preview);
viewer.appendChild.mockImplementation(containerElement => {
container = containerElement;
});
 
Loading
Loading
@@ -108,7 +112,7 @@ describe('BalsamiqViewer', () => {
});
 
it('should call .renderPreview for each preview', () => {
const allArgs = balsamiqViewer.renderPreview.calls.allArgs();
const allArgs = balsamiqViewer.renderPreview.mock.calls;
 
expect(allArgs.length).toBe(2);
 
Loading
Loading
@@ -132,19 +136,15 @@ describe('BalsamiqViewer', () => {
});
 
describe('initDatabase', () => {
let database;
let uint8Array;
let data;
 
beforeEach(() => {
uint8Array = {};
database = {};
data = 'data';
balsamiqViewer = {};
spyOn(window, 'Uint8Array').and.returnValue(uint8Array);
spyOn(sqljs, 'Database').and.returnValue(database);
window.Uint8Array = jest.fn();
window.Uint8Array.mockReturnValue(uint8Array);
 
BalsamiqViewer.prototype.initDatabase.call(balsamiqViewer, data);
});
Loading
Loading
@@ -158,7 +158,7 @@ describe('BalsamiqViewer', () => {
});
 
it('should set .database', () => {
expect(balsamiqViewer.database).toBe(database);
expect(balsamiqViewer.database).not.toBe(null);
});
});
 
Loading
Loading
@@ -168,15 +168,17 @@ describe('BalsamiqViewer', () => {
let getPreviews;
 
beforeEach(() => {
database = jasmine.createSpyObj('database', ['exec']);
database = {
exec: jest.fn(),
};
thumbnails = [{ values: [0, 1, 2] }];
 
balsamiqViewer = {
database,
};
 
spyOn(BalsamiqViewer, 'parsePreview').and.callFake(preview => preview.toString());
database.exec.and.returnValue(thumbnails);
jest.spyOn(BalsamiqViewer, 'parsePreview').mockImplementation(preview => preview.toString());
database.exec.mockReturnValue(thumbnails);
 
getPreviews = BalsamiqViewer.prototype.getPreviews.call(balsamiqViewer);
});
Loading
Loading
@@ -186,7 +188,7 @@ describe('BalsamiqViewer', () => {
});
 
it('should call .parsePreview for each value', () => {
const allArgs = BalsamiqViewer.parsePreview.calls.allArgs();
const allArgs = BalsamiqViewer.parsePreview.mock.calls;
 
expect(allArgs.length).toBe(3);
 
Loading
Loading
@@ -207,7 +209,9 @@ describe('BalsamiqViewer', () => {
let getResource;
 
beforeEach(() => {
database = jasmine.createSpyObj('database', ['exec']);
database = {
exec: jest.fn(),
};
resourceID = 4;
resource = ['resource'];
 
Loading
Loading
@@ -215,7 +219,7 @@ describe('BalsamiqViewer', () => {
database,
};
 
database.exec.and.returnValue(resource);
database.exec.mockReturnValue(resource);
 
getResource = BalsamiqViewer.prototype.getResource.call(balsamiqViewer, resourceID);
});
Loading
Loading
@@ -241,14 +245,18 @@ describe('BalsamiqViewer', () => {
innerHTML = '<a>innerHTML</a>';
previewElement = {
outerHTML: '<p>outerHTML</p>',
classList: jasmine.createSpyObj('classList', ['add']),
classList: {
add: jest.fn(),
},
};
preview = {};
 
balsamiqViewer = jasmine.createSpyObj('balsamiqViewer', ['renderTemplate']);
balsamiqViewer = {
renderTemplate: jest.fn(),
};
 
spyOn(document, 'createElement').and.returnValue(previewElement);
balsamiqViewer.renderTemplate.and.returnValue(innerHTML);
jest.spyOn(document, 'createElement').mockReturnValue(previewElement);
balsamiqViewer.renderTemplate.mockReturnValue(innerHTML);
 
renderPreview = BalsamiqViewer.prototype.renderPreview.call(balsamiqViewer, preview);
});
Loading
Loading
@@ -290,10 +298,12 @@ describe('BalsamiqViewer', () => {
</div>
`;
 
balsamiqViewer = jasmine.createSpyObj('balsamiqViewer', ['getResource']);
balsamiqViewer = {
getResource: jest.fn(),
};
 
spyOn(BalsamiqViewer, 'parseTitle').and.returnValue(name);
balsamiqViewer.getResource.and.returnValue(resource);
jest.spyOn(BalsamiqViewer, 'parseTitle').mockReturnValue(name);
balsamiqViewer.getResource.mockReturnValue(resource);
 
renderTemplate = BalsamiqViewer.prototype.renderTemplate.call(balsamiqViewer, preview);
});
Loading
Loading
@@ -306,7 +316,7 @@ describe('BalsamiqViewer', () => {
expect(BalsamiqViewer.parseTitle).toHaveBeenCalledWith(resource);
});
 
it('should return the template string', function() {
it('should return the template string', () => {
expect(renderTemplate.replace(/\s/g, '')).toEqual(template.replace(/\s/g, ''));
});
});
Loading
Loading
@@ -318,7 +328,7 @@ describe('BalsamiqViewer', () => {
beforeEach(() => {
preview = ['{}', '{ "id": 1 }'];
 
spyOn(JSON, 'parse').and.callThrough();
jest.spyOn(JSON, 'parse');
 
parsePreview = BalsamiqViewer.parsePreview(preview);
});
Loading
Loading
@@ -337,7 +347,7 @@ describe('BalsamiqViewer', () => {
beforeEach(() => {
title = { values: [['{}', '{}', '{"name":"name"}']] };
 
spyOn(JSON, 'parse').and.callThrough();
jest.spyOn(JSON, 'parse');
 
parseTitle = BalsamiqViewer.parseTitle(title);
});
Loading
Loading
Loading
Loading
@@ -440,23 +440,6 @@ describe('boardsStore', () => {
});
});
 
describe('allBoards', () => {
const url = `${endpoints.boardsEndpoint}.json`;
it('makes a request to fetch all boards', () => {
axiosMock.onGet(url).replyOnce(200, dummyResponse);
const expectedResponse = expect.objectContaining({ data: dummyResponse });
return expect(boardsStore.allBoards()).resolves.toEqual(expectedResponse);
});
it('fails for error response', () => {
axiosMock.onGet(url).replyOnce(500);
return expect(boardsStore.allBoards()).rejects.toThrow();
});
});
describe('recentBoards', () => {
const url = `${endpoints.recentBoardsEndpoint}.json`;
 
Loading
Loading
import Vue from 'vue';
import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
import { GlDropdown } from '@gitlab/ui';
import { GlDropdown, GlLoadingIcon } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants';
import BoardsSelector from '~/boards/components/boards_selector.vue';
import boardsStore from '~/boards/stores/boards_store';
Loading
Loading
@@ -8,7 +8,8 @@ import boardsStore from '~/boards/stores/boards_store';
const throttleDuration = 1;
 
function boardGenerator(n) {
return new Array(n).fill().map((board, id) => {
return new Array(n).fill().map((board, index) => {
const id = `${index}`;
const name = `board${id}`;
 
return {
Loading
Loading
@@ -34,8 +35,17 @@ describe('BoardsSelector', () => {
 
const getDropdownItems = () => wrapper.findAll('.js-dropdown-item');
const getDropdownHeaders = () => wrapper.findAll('.dropdown-bold-header');
const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
 
beforeEach(() => {
const $apollo = {
queries: {
boards: {
loading: false,
},
},
};
boardsStore.setEndpoints({
boardsEndpoint: '',
recentBoardsEndpoint: '',
Loading
Loading
@@ -45,7 +55,13 @@ describe('BoardsSelector', () => {
});
 
allBoardsResponse = Promise.resolve({
data: boards,
data: {
group: {
boards: {
edges: boards.map(board => ({ node: board })),
},
},
},
});
recentBoardsResponse = Promise.resolve({
data: recentBoards,
Loading
Loading
@@ -54,8 +70,7 @@ describe('BoardsSelector', () => {
boardsStore.allBoards = jest.fn(() => allBoardsResponse);
boardsStore.recentBoards = jest.fn(() => recentBoardsResponse);
 
const Component = Vue.extend(BoardsSelector);
wrapper = mount(Component, {
wrapper = mount(BoardsSelector, {
propsData: {
throttleDuration,
currentBoard: {
Loading
Loading
@@ -77,13 +92,18 @@ describe('BoardsSelector', () => {
scopedIssueBoardFeatureEnabled: true,
weights: [],
},
mocks: { $apollo },
attachToDocument: true,
});
 
wrapper.vm.$apollo.addSmartQuery = jest.fn((_, options) => {
wrapper.setData({
[options.loadingKey]: true,
});
});
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
wrapper.find(GlDropdown).vm.$emit('show');
return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => Vue.nextTick());
});
 
afterEach(() => {
Loading
Loading
@@ -91,64 +111,99 @@ describe('BoardsSelector', () => {
wrapper = null;
});
 
describe('filtering', () => {
it('shows all boards without filtering', () => {
expect(getDropdownItems().length).toBe(boards.length + recentBoards.length);
describe('loading', () => {
// we are testing loading state, so don't resolve responses until after the tests
afterEach(() => {
return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
});
 
it('shows only matching boards when filtering', () => {
const filterTerm = 'board1';
const expectedCount = boards.filter(board => board.name.includes(filterTerm)).length;
it('shows loading spinner', () => {
expect(getDropdownHeaders()).toHaveLength(0);
expect(getDropdownItems()).toHaveLength(0);
expect(getLoadingIcon().exists()).toBe(true);
});
});
 
fillSearchBox(filterTerm);
describe('loaded', () => {
beforeEach(() => {
return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
});
 
return Vue.nextTick().then(() => {
expect(getDropdownItems().length).toBe(expectedCount);
});
it('hides loading spinner', () => {
expect(getLoadingIcon().exists()).toBe(false);
});
 
it('shows message if there are no matching boards', () => {
fillSearchBox('does not exist');
describe('filtering', () => {
beforeEach(() => {
wrapper.setData({
boards,
});
 
return Vue.nextTick().then(() => {
expect(getDropdownItems().length).toBe(0);
expect(wrapper.text().includes('No matching boards found')).toBe(true);
return nextTick();
});
});
});
 
describe('recent boards section', () => {
it('shows only when boards are greater than 10', () => {
const expectedCount = 2; // Recent + All
it('shows all boards without filtering', () => {
expect(getDropdownItems()).toHaveLength(boards.length + recentBoards.length);
});
 
expect(getDropdownHeaders().length).toBe(expectedCount);
});
it('shows only matching boards when filtering', () => {
const filterTerm = 'board1';
const expectedCount = boards.filter(board => board.name.includes(filterTerm)).length;
 
it('does not show when boards are less than 10', () => {
wrapper.setData({
boards: boards.slice(0, 5),
fillSearchBox(filterTerm);
return nextTick().then(() => {
expect(getDropdownItems()).toHaveLength(expectedCount);
});
});
 
return Vue.nextTick().then(() => {
expect(getDropdownHeaders().length).toBe(0);
it('shows message if there are no matching boards', () => {
fillSearchBox('does not exist');
return nextTick().then(() => {
expect(getDropdownItems()).toHaveLength(0);
expect(wrapper.text().includes('No matching boards found')).toBe(true);
});
});
});
 
it('does not show when recentBoards api returns empty array', () => {
wrapper.setData({
recentBoards: [],
describe('recent boards section', () => {
it('shows only when boards are greater than 10', () => {
wrapper.setData({
boards,
});
return nextTick().then(() => {
expect(getDropdownHeaders()).toHaveLength(2);
});
});
 
return Vue.nextTick().then(() => {
expect(getDropdownHeaders().length).toBe(0);
it('does not show when boards are less than 10', () => {
wrapper.setData({
boards: boards.slice(0, 5),
});
return nextTick().then(() => {
expect(getDropdownHeaders()).toHaveLength(0);
});
});
it('does not show when recentBoards api returns empty array', () => {
wrapper.setData({
recentBoards: [],
});
return nextTick().then(() => {
expect(getDropdownHeaders()).toHaveLength(0);
});
});
});
 
it('does not show when search is active', () => {
fillSearchBox('Random string');
it('does not show when search is active', () => {
fillSearchBox('Random string');
 
return Vue.nextTick().then(() => {
expect(getDropdownHeaders().length).toBe(0);
return nextTick().then(() => {
expect(getDropdownHeaders()).toHaveLength(0);
});
});
});
});
Loading
Loading
import { mount, shallowMount } from '@vue/test-utils';
import projectFeatureSetting from '~/pages/projects/shared/permissions/components/project_feature_setting.vue';
import projectFeatureToggle from '~/vue_shared/components/toggle_button.vue';
describe('Project Feature Settings', () => {
const defaultProps = {
name: 'Test',
options: [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]],
value: 1,
disabledInput: false,
};
let wrapper;
const mountComponent = customProps => {
const propsData = { ...defaultProps, ...customProps };
return shallowMount(projectFeatureSetting, { propsData });
};
beforeEach(() => {
wrapper = mountComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('Hidden name input', () => {
it('should set the hidden name input if the name exists', () => {
expect(wrapper.find({ name: 'Test' }).props().value).toBe(1);
});
it('should not set the hidden name input if the name does not exist', () => {
wrapper.setProps({ name: null });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ name: 'Test' }).exists()).toBe(false);
});
});
});
describe('Feature toggle', () => {
it('should enable the feature toggle if the value is not 0', () => {
expect(wrapper.find(projectFeatureToggle).props().value).toBe(true);
});
it('should enable the feature toggle if the value is less than 0', () => {
wrapper.setProps({ value: -1 });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find(projectFeatureToggle).props().value).toBe(true);
});
});
it('should disable the feature toggle if the value is 0', () => {
wrapper.setProps({ value: 0 });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find(projectFeatureToggle).props().value).toBe(false);
});
});
it('should disable the feature toggle if disabledInput is set', () => {
wrapper.setProps({ disabledInput: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find(projectFeatureToggle).props().disabledInput).toBe(true);
});
});
it('should emit a change event when the feature toggle changes', () => {
// Needs to be fully mounted to be able to trigger the click event on the internal button
wrapper = mount(projectFeatureSetting, { propsData: defaultProps });
expect(wrapper.emitted().change).toBeUndefined();
wrapper
.find(projectFeatureToggle)
.find('button')
.trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted().change.length).toBe(1);
expect(wrapper.emitted().change[0]).toEqual([0]);
});
});
});
describe('Project repo select', () => {
it.each`
disabledInput | value | options | isDisabled
${true} | ${0} | ${[[1, 1]]} | ${true}
${true} | ${1} | ${[[1, 1], [2, 2], [3, 3]]} | ${true}
${false} | ${0} | ${[[1, 1], [2, 2], [3, 3]]} | ${true}
${false} | ${1} | ${[[1, 1]]} | ${true}
${false} | ${1} | ${[[1, 1], [2, 2], [3, 3]]} | ${false}
`(
'should set disabled to $isDisabled when disabledInput is $disabledInput, the value is $value and options are $options',
({ disabledInput, value, options, isDisabled }) => {
wrapper.setProps({ disabledInput, value, options });
return wrapper.vm.$nextTick(() => {
if (isDisabled) {
expect(wrapper.find('select').attributes().disabled).toEqual('disabled');
} else {
expect(wrapper.find('select').attributes().disabled).toBeUndefined();
}
});
},
);
it('should emit the change when a new option is selected', () => {
expect(wrapper.emitted().change).toBeUndefined();
wrapper
.findAll('option')
.at(1)
.trigger('change');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted().change.length).toBe(1);
expect(wrapper.emitted().change[0]).toEqual([2]);
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import projectSettingRow from '~/pages/projects/shared/permissions/components/project_setting_row.vue';
describe('Project Setting Row', () => {
let wrapper;
const mountComponent = (customProps = {}) => {
const propsData = { ...customProps };
return shallowMount(projectSettingRow, { propsData });
};
beforeEach(() => {
wrapper = mountComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('should show the label if it is set', () => {
wrapper.setProps({ label: 'Test label' });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find('label').text()).toEqual('Test label');
});
});
it('should hide the label if it is not set', () => {
expect(wrapper.find('label').exists()).toBe(false);
});
it('should show the help icon with the correct help path if it is set', () => {
wrapper.setProps({ label: 'Test label', helpPath: '/123' });
return wrapper.vm.$nextTick(() => {
const link = wrapper.find('a');
expect(link.exists()).toBe(true);
expect(link.attributes().href).toEqual('/123');
});
});
it('should hide the help icon if no help path is set', () => {
wrapper.setProps({ label: 'Test label' });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find('a').exists()).toBe(false);
});
});
it('should show the help text if it is set', () => {
wrapper.setProps({ helpText: 'Test text' });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find('span').text()).toEqual('Test text');
});
});
it('should hide the help text if it is set', () => {
expect(wrapper.find('span').exists()).toBe(false);
});
});
import { shallowMount } from '@vue/test-utils';
import settingsPanel from '~/pages/projects/shared/permissions/components/settings_panel.vue';
import {
featureAccessLevel,
visibilityLevelDescriptions,
visibilityOptions,
} from '~/pages/projects/shared/permissions/constants';
const defaultProps = {
currentSettings: {
visibilityLevel: 10,
requestAccessEnabled: true,
issuesAccessLevel: 20,
repositoryAccessLevel: 20,
forkingAccessLevel: 20,
mergeRequestsAccessLevel: 20,
buildsAccessLevel: 20,
wikiAccessLevel: 20,
snippetsAccessLevel: 20,
pagesAccessLevel: 10,
containerRegistryEnabled: true,
lfsEnabled: true,
emailsDisabled: false,
packagesEnabled: true,
},
canDisableEmails: true,
canChangeVisibilityLevel: true,
allowedVisibilityOptions: [0, 10, 20],
visibilityHelpPath: '/help/public_access/public_access',
registryAvailable: false,
registryHelpPath: '/help/user/packages/container_registry/index',
lfsAvailable: true,
lfsHelpPath: '/help/workflow/lfs/manage_large_binaries_with_git_lfs',
pagesAvailable: true,
pagesAccessControlEnabled: false,
pagesAccessControlForced: false,
pagesHelpPath: '/help/user/project/pages/introduction#gitlab-pages-access-control-core',
packagesAvailable: false,
packagesHelpPath: '/help/user/packages/index',
};
describe('Settings Panel', () => {
let wrapper;
const mountComponent = customProps => {
const propsData = { ...defaultProps, ...customProps };
return shallowMount(settingsPanel, { propsData });
};
const overrideCurrentSettings = (currentSettingsProps, extraProps = {}) => {
return mountComponent({
...extraProps,
currentSettings: {
...defaultProps.currentSettings,
...currentSettingsProps,
},
});
};
beforeEach(() => {
wrapper = mountComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('Project Visibility', () => {
it('should set the project visibility help path', () => {
expect(wrapper.find({ ref: 'project-visibility-settings' }).props().helpPath).toBe(
defaultProps.visibilityHelpPath,
);
});
it('should not disable the visibility level dropdown', () => {
wrapper.setProps({ canChangeVisibilityLevel: true });
return wrapper.vm.$nextTick(() => {
expect(
wrapper.find('[name="project[visibility_level]"]').attributes().disabled,
).toBeUndefined();
});
});
it('should disable the visibility level dropdown', () => {
wrapper.setProps({ canChangeVisibilityLevel: false });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find('[name="project[visibility_level]"]').attributes().disabled).toBe(
'disabled',
);
});
});
it.each`
option | allowedOptions | disabled
${visibilityOptions.PRIVATE} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false}
${visibilityOptions.PRIVATE} | ${[visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${true}
${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false}
${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.PUBLIC]} | ${true}
${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false}
${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL]} | ${true}
`(
'sets disabled to $disabled for the visibility option $option when given $allowedOptions',
({ option, allowedOptions, disabled }) => {
wrapper.setProps({ allowedVisibilityOptions: allowedOptions });
return wrapper.vm.$nextTick(() => {
const attributeValue = wrapper
.find(`[name="project[visibility_level]"] option[value="${option}"]`)
.attributes().disabled;
if (disabled) {
expect(attributeValue).toBe('disabled');
} else {
expect(attributeValue).toBeUndefined();
}
});
},
);
it('should set the visibility level description based upon the selected visibility level', () => {
wrapper.find('[name="project[visibility_level]"]').setValue(visibilityOptions.INTERNAL);
expect(wrapper.find({ ref: 'project-visibility-settings' }).text()).toContain(
visibilityLevelDescriptions[visibilityOptions.INTERNAL],
);
});
it('should show the request access checkbox if the visibility level is not private', () => {
wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.INTERNAL });
expect(wrapper.find('[name="project[request_access_enabled]"]').exists()).toBe(true);
});
it('should not show the request access checkbox if the visibility level is private', () => {
wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PRIVATE });
expect(wrapper.find('[name="project[request_access_enabled]"]').exists()).toBe(false);
});
});
describe('Repository', () => {
it('should set the repository help text when the visibility level is set to private', () => {
wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PRIVATE });
expect(wrapper.find({ ref: 'repository-settings' }).props().helpText).toEqual(
'View and edit files in this project',
);
});
it('should set the repository help text with a read access warning when the visibility level is set to non-private', () => {
wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PUBLIC });
expect(wrapper.find({ ref: 'repository-settings' }).props().helpText).toEqual(
'View and edit files in this project. Non-project members will only have read access',
);
});
});
describe('Merge requests', () => {
it('should enable the merge requests access level input when the repository is enabled', () => {
wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.EVERYONE });
expect(
wrapper
.find('[name="project[project_feature_attributes][merge_requests_access_level]"]')
.props().disabledInput,
).toEqual(false);
});
it('should disable the merge requests access level input when the repository is disabled', () => {
wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED });
expect(
wrapper
.find('[name="project[project_feature_attributes][merge_requests_access_level]"]')
.props().disabledInput,
).toEqual(true);
});
});
describe('Forks', () => {
it('should enable the forking access level input when the repository is enabled', () => {
wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.EVERYONE });
expect(
wrapper.find('[name="project[project_feature_attributes][forking_access_level]"]').props()
.disabledInput,
).toEqual(false);
});
it('should disable the forking access level input when the repository is disabled', () => {
wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED });
expect(
wrapper.find('[name="project[project_feature_attributes][forking_access_level]"]').props()
.disabledInput,
).toEqual(true);
});
});
describe('Pipelines', () => {
it('should enable the builds access level input when the repository is enabled', () => {
wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.EVERYONE });
expect(
wrapper.find('[name="project[project_feature_attributes][builds_access_level]"]').props()
.disabledInput,
).toEqual(false);
});
it('should disable the builds access level input when the repository is disabled', () => {
wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED });
expect(
wrapper.find('[name="project[project_feature_attributes][builds_access_level]"]').props()
.disabledInput,
).toEqual(true);
});
});
describe('Container registry', () => {
it('should show the container registry settings if the registry is available', () => {
wrapper.setProps({ registryAvailable: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'container-registry-settings' }).exists()).toBe(true);
});
});
it('should hide the container registry settings if the registry is not available', () => {
wrapper.setProps({ registryAvailable: false });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'container-registry-settings' }).exists()).toBe(false);
});
});
it('should set the container registry settings help path', () => {
wrapper.setProps({ registryAvailable: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'container-registry-settings' }).props().helpPath).toBe(
defaultProps.registryHelpPath,
);
});
});
it('should show the container registry public note if the visibility level is public and the registry is available', () => {
wrapper = overrideCurrentSettings(
{ visibilityLevel: visibilityOptions.PUBLIC },
{ registryAvailable: true },
);
expect(wrapper.find({ ref: 'container-registry-settings' }).text()).toContain(
'Note: the container registry is always visible when a project is public',
);
});
it('should hide the container registry public note if the visibility level is private and the registry is available', () => {
wrapper = overrideCurrentSettings(
{ visibilityLevel: visibilityOptions.PRIVATE },
{ registryAvailable: true },
);
expect(wrapper.find({ ref: 'container-registry-settings' }).text()).not.toContain(
'Note: the container registry is always visible when a project is public',
);
});
it('should enable the container registry input when the repository is enabled', () => {
wrapper = overrideCurrentSettings(
{ repositoryAccessLevel: featureAccessLevel.EVERYONE },
{ registryAvailable: true },
);
expect(
wrapper.find('[name="project[container_registry_enabled]"]').props().disabledInput,
).toEqual(false);
});
it('should disable the container registry input when the repository is disabled', () => {
wrapper = overrideCurrentSettings(
{ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
{ registryAvailable: true },
);
expect(
wrapper.find('[name="project[container_registry_enabled]"]').props().disabledInput,
).toEqual(true);
});
});
describe('Git Large File Storage', () => {
it('should show the LFS settings if LFS is available', () => {
wrapper.setProps({ lfsAvailable: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'git-lfs-settings' }).exists()).toEqual(true);
});
});
it('should hide the LFS settings if LFS is not available', () => {
wrapper.setProps({ lfsAvailable: false });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'git-lfs-settings' }).exists()).toEqual(false);
});
});
it('should set the LFS settings help path', () => {
expect(wrapper.find({ ref: 'git-lfs-settings' }).props().helpPath).toBe(
defaultProps.lfsHelpPath,
);
});
it('should enable the LFS input when the repository is enabled', () => {
wrapper = overrideCurrentSettings(
{ repositoryAccessLevel: featureAccessLevel.EVERYONE },
{ lfsAvailable: true },
);
expect(wrapper.find('[name="project[lfs_enabled]"]').props().disabledInput).toEqual(false);
});
it('should disable the LFS input when the repository is disabled', () => {
wrapper = overrideCurrentSettings(
{ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
{ lfsAvailable: true },
);
expect(wrapper.find('[name="project[lfs_enabled]"]').props().disabledInput).toEqual(true);
});
});
describe('Packages', () => {
it('should show the packages settings if packages are available', () => {
wrapper.setProps({ packagesAvailable: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'package-settings' }).exists()).toEqual(true);
});
});
it('should hide the packages settings if packages are not available', () => {
wrapper.setProps({ packagesAvailable: false });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'package-settings' }).exists()).toEqual(false);
});
});
it('should set the package settings help path', () => {
wrapper.setProps({ packagesAvailable: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'package-settings' }).props().helpPath).toBe(
defaultProps.packagesHelpPath,
);
});
});
it('should enable the packages input when the repository is enabled', () => {
wrapper = overrideCurrentSettings(
{ repositoryAccessLevel: featureAccessLevel.EVERYONE },
{ packagesAvailable: true },
);
expect(wrapper.find('[name="project[packages_enabled]"]').props().disabledInput).toEqual(
false,
);
});
it('should disable the packages input when the repository is disabled', () => {
wrapper = overrideCurrentSettings(
{ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
{ packagesAvailable: true },
);
expect(wrapper.find('[name="project[packages_enabled]"]').props().disabledInput).toEqual(
true,
);
});
});
describe('Pages', () => {
it.each`
pagesAvailable | pagesAccessControlEnabled | visibility
${true} | ${true} | ${'show'}
${true} | ${false} | ${'hide'}
${false} | ${true} | ${'hide'}
${false} | ${false} | ${'hide'}
`(
'should $visibility the page settings if pagesAvailable is $pagesAvailable and pagesAccessControlEnabled is $pagesAccessControlEnabled',
({ pagesAvailable, pagesAccessControlEnabled, visibility }) => {
wrapper.setProps({ pagesAvailable, pagesAccessControlEnabled });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'pages-settings' }).exists()).toBe(visibility === 'show');
});
},
);
it('should set the pages settings help path', () => {
wrapper.setProps({ pagesAvailable: true, pagesAccessControlEnabled: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'pages-settings' }).props().helpPath).toBe(
defaultProps.pagesHelpPath,
);
});
});
});
describe('Email notifications', () => {
it('should show the disable email notifications input if emails an be disabled', () => {
wrapper.setProps({ canDisableEmails: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'email-settings' }).exists()).toBe(true);
});
});
it('should hide the disable email notifications input if emails cannot be disabled', () => {
wrapper.setProps({ canDisableEmails: false });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find({ ref: 'email-settings' }).exists()).toBe(false);
});
});
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Snippet Description Edit component rendering matches the snapshot 1`] = `
<div
class="form-group js-description-input"
>
<label>
Description (optional)
</label>
<div
class="js-collapsible-input"
>
<div
class="js-collapsed d-none"
>
<gl-form-input-stub
class="form-control"
data-qa-selector="description_placeholder"
placeholder="Optionally add a description about what your snippet does or how to use it…"
/>
</div>
<markdown-field-stub
addspacingclasses="true"
canattachfile="true"
class="js-expanded"
enableautocomplete="true"
helppagepath=""
markdowndocspath="help/"
markdownpreviewpath="foo/"
note="[object Object]"
quickactionsdocspath=""
textareavalue=""
>
<textarea
aria-label="Description"
class="note-textarea js-gfm-input js-autosize markdown-area
qa-description-textarea"
data-supports-quick-actions="false"
dir="auto"
id="snippet-description"
placeholder="Write a comment or drag your files here…"
/>
</markdown-field-stub>
</div>
</div>
`;
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue';
import { shallowMount } from '@vue/test-utils';
describe('Snippet Description Edit component', () => {
let wrapper;
const defaultDescription = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
const markdownPreviewPath = 'foo/';
const markdownDocsPath = 'help/';
function createComponent(description = defaultDescription) {
wrapper = shallowMount(SnippetDescriptionEdit, {
propsData: {
description,
markdownPreviewPath,
markdownDocsPath,
},
});
}
function isHidden(sel) {
return wrapper.find(sel).classes('d-none');
}
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('rendering', () => {
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('renders the field expanded when description exists', () => {
expect(wrapper.find('.js-collapsed').classes('d-none')).toBe(true);
expect(wrapper.find('.js-expanded').classes('d-none')).toBe(false);
expect(isHidden('.js-collapsed')).toBe(true);
expect(isHidden('.js-expanded')).toBe(false);
});
it('renders the field collapsed if there is no description yet', () => {
createComponent('');
expect(isHidden('.js-collapsed')).toBe(false);
expect(isHidden('.js-expanded')).toBe(true);
});
});
});
Loading
Loading
@@ -259,16 +259,40 @@ describe('mrWidgetOptions', () => {
 
describe('methods', () => {
describe('checkStatus', () => {
it('should tell service to check status', () => {
let cb;
let isCbExecuted;
beforeEach(() => {
jest.spyOn(vm.service, 'checkStatus').mockReturnValue(returnPromise(mockData));
jest.spyOn(vm.mr, 'setData').mockImplementation(() => {});
jest.spyOn(vm, 'handleNotification').mockImplementation(() => {});
 
let isCbExecuted = false;
const cb = () => {
isCbExecuted = false;
cb = () => {
isCbExecuted = true;
};
});
it('should not tell service to check status if document is not visible', () => {
Object.defineProperty(document, 'visibilityState', {
value: 'hidden',
configurable: true,
});
vm.checkStatus(cb);
return vm.$nextTick().then(() => {
expect(vm.service.checkStatus).not.toHaveBeenCalled();
expect(vm.mr.setData).not.toHaveBeenCalled();
expect(vm.handleNotification).not.toHaveBeenCalled();
expect(isCbExecuted).toBeFalsy();
Object.defineProperty(document, 'visibilityState', {
value: 'visible',
configurable: true,
});
});
});
 
it('should tell service to check status if document is visible', () => {
vm.checkStatus(cb);
 
return vm.$nextTick().then(() => {
Loading
Loading
Loading
Loading
@@ -52,7 +52,7 @@ describe ProjectPolicy do
admin_snippet admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image
admin_pipeline admin_environment admin_deployment destroy_release add_cluster
daily_statistics read_deploy_token
daily_statistics read_deploy_token create_deploy_token
]
end
 
Loading
Loading
Loading
Loading
@@ -133,4 +133,57 @@ describe API::DeployTokens do
end
end
end
describe 'POST /projects/:id/deploy_tokens' do
let(:params) do
{
name: 'Foo',
expires_at: 1.year.from_now,
scopes: [
'read_repository'
],
username: 'Bar'
}
end
subject do
post api("/projects/#{project.id}/deploy_tokens", user), params: params
response
end
context 'when unauthenticated' do
let(:user) { nil }
it { is_expected.to have_gitlab_http_status(:not_found) }
end
context 'when authenticated as non-admin user' do
before do
project.add_developer(user)
end
it { is_expected.to have_gitlab_http_status(:forbidden) }
end
context 'when authenticated as maintainer' do
before do
project.add_maintainer(user)
end
it 'creates the deploy token' do
expect { subject }.to change { DeployToken.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/deploy_token')
end
context 'with an invalid scope' do
before do
params[:scopes] = %w[read_repository all_access]
end
it { is_expected.to have_gitlab_http_status(:bad_request) }
end
end
end
end
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