Commit de9bd29a authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents ba36dca3 2a9675e9
......@@ -5,7 +5,7 @@ import {
GlSprintf,
GlLink,
GlToggle,
GlDeprecatedButton,
GlButton,
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlIcon,
......@@ -25,7 +25,7 @@ export default {
GlSprintf,
GlLink,
GlToggle,
GlDeprecatedButton,
GlButton,
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlIcon,
......@@ -232,18 +232,24 @@ export default {
</gl-deprecated-dropdown>
</div>
</div>
<div v-if="showButtons" class="mt-3">
<gl-deprecated-button
class="btn-success inline mr-1"
<div v-if="showButtons" class="gl-mt-5 gl-display-flex">
<gl-button
variant="success"
category="primary"
data-qa-selector="save_ingress_modsecurity_settings"
:loading="saving"
:disabled="saveButtonDisabled"
@click="updateApplication"
>
{{ saveButtonLabel }}
</gl-deprecated-button>
<gl-deprecated-button :disabled="saveButtonDisabled" @click="resetStatus">
</gl-button>
<gl-button
data-qa-selector="cancel_ingress_modsecurity_settings"
:disabled="saveButtonDisabled"
@click="resetStatus"
>
{{ __('Cancel') }}
</gl-deprecated-button>
</gl-button>
</div>
</div>
</div>
......
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
import Translate from '~/vue_shared/translate';
import store from './store';
import RegistrySettingsApp from './components/registry_settings_app.vue';
import { apolloProvider } from './graphql/index';
......@@ -13,11 +12,9 @@ export default () => {
if (!el) {
return null;
}
store.dispatch('setInitialState', el.dataset);
const { projectPath, isAdmin, adminSettingsPath, enableHistoricEntries } = el.dataset;
return new Vue({
el,
store,
apolloProvider,
components: {
RegistrySettingsApp,
......
import Api from '~/api';
import * as types from './mutation_types';
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
export const updateSettings = ({ commit }, data) => commit(types.UPDATE_SETTINGS, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING);
export const receiveSettingsSuccess = ({ commit }, data) => {
commit(types.SET_SETTINGS, data);
};
export const resetSettings = ({ commit }) => commit(types.RESET_SETTINGS);
export const fetchSettings = ({ dispatch, state }) => {
dispatch('toggleLoading');
return Api.project(state.projectId)
.then(({ data: { container_expiration_policy } }) =>
dispatch('receiveSettingsSuccess', container_expiration_policy),
)
.finally(() => dispatch('toggleLoading'));
};
export const saveSettings = ({ dispatch, state }) => {
dispatch('toggleLoading');
return Api.updateProject(state.projectId, {
container_expiration_policy_attributes: state.settings,
})
.then(({ data: { container_expiration_policy } }) =>
dispatch('receiveSettingsSuccess', container_expiration_policy),
)
.finally(() => dispatch('toggleLoading'));
};
import { isEqual } from 'lodash';
import { findDefaultOption } from '../../shared/utils';
export const getCadence = state =>
state.settings.cadence || findDefaultOption(state.formOptions.cadence);
export const getKeepN = state =>
state.settings.keep_n || findDefaultOption(state.formOptions.keepN);
export const getOlderThan = state =>
state.settings.older_than || findDefaultOption(state.formOptions.olderThan);
export const getSettings = (state, getters) => ({
enabled: state.settings.enabled,
cadence: getters.getCadence,
older_than: getters.getOlderThan,
keep_n: getters.getKeepN,
name_regex: state.settings.name_regex,
name_regex_keep: state.settings.name_regex_keep,
});
export const getIsEdited = state => !isEqual(state.original, state.settings);
export const getIsDisabled = state => {
return !(state.original || state.enableHistoricEntries);
};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import * as getters from './getters';
import state from './state';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
state,
actions,
mutations,
getters,
});
export default createStore();
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
export const UPDATE_SETTINGS = 'UPDATE_SETTINGS';
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
export const SET_SETTINGS = 'SET_SETTINGS';
export const RESET_SETTINGS = 'RESET_SETTINGS';
import { parseBoolean } from '~/lib/utils/common_utils';
import * as types from './mutation_types';
export default {
[types.SET_INITIAL_STATE](state, initialState) {
state.projectId = initialState.projectId;
state.formOptions = {
cadence: JSON.parse(initialState.cadenceOptions),
keepN: JSON.parse(initialState.keepNOptions),
olderThan: JSON.parse(initialState.olderThanOptions),
};
state.enableHistoricEntries = parseBoolean(initialState.enableHistoricEntries);
state.isAdmin = parseBoolean(initialState.isAdmin);
state.adminSettingsPath = initialState.adminSettingsPath;
},
[types.UPDATE_SETTINGS](state, data) {
state.settings = { ...state.settings, ...data.settings };
},
[types.SET_SETTINGS](state, settings) {
state.settings = settings ?? state.settings;
state.original = Object.freeze(settings);
},
[types.RESET_SETTINGS](state) {
state.settings = { ...state.original };
},
[types.TOGGLE_LOADING](state) {
state.isLoading = !state.isLoading;
},
};
export default () => ({
/*
* Project Id used to build the API call
*/
projectId: '',
/*
* Boolean to determine if the UI is loading data from the API
*/
isLoading: false,
/*
* Boolean to determine if the user is an admin
*/
isAdmin: false,
/*
* String containing the full path to the admin config page for CI/CD
*/
adminSettingsPath: '',
/*
* Boolean to determine if project created before 12.8 can use this feature
*/
enableHistoricEntries: false,
/*
* This contains the data shown and manipulated in the UI
* Has the following structure:
* {
* enabled: Boolean
* cadence: String,
* older_than: String,
* keep_n: String,
* name_regex: String
* }
*/
settings: {},
/*
* Same structure as settings, above but Frozen object and used only in case the user clicks 'cancel', initialized to null
*/
original: null,
/*
* Contains the options used to populate the form selects
*/
formOptions: {},
});
# frozen_string_literal: true
module Packages
module Generic
class PackageFinder
def initialize(project)
@project = project
end
def execute!(package_name, package_version)
project
.packages
.generic
.by_name_and_version!(package_name, package_version)
end
private
attr_reader :project
end
end
end
......@@ -26,7 +26,7 @@ class Packages::Package < ApplicationRecord
validates :project, presence: true
validates :name, presence: true
validates :name, format: { with: Gitlab::Regex.package_name_regex }, unless: :conan?
validates :name, format: { with: Gitlab::Regex.package_name_regex }, unless: -> { conan? || generic? }
validates :name,
uniqueness: { scope: %i[project_id version package_type] }, unless: :conan?
......@@ -35,8 +35,9 @@ class Packages::Package < ApplicationRecord
validate :valid_npm_package_name, if: :npm?
validate :valid_composer_global_name, if: :composer?
validate :package_already_taken, if: :npm?
validates :version, format: { with: Gitlab::Regex.semver_regex }, if: -> { npm? || nuget? }
validates :name, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :name, format: { with: Gitlab::Regex.generic_package_name_regex }, if: :generic?
validates :version, format: { with: Gitlab::Regex.semver_regex }, if: -> { npm? || nuget? }
validates :version, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :version, format: { with: Gitlab::Regex.maven_version_regex }, if: -> { version? && maven? }
validates :version, format: { with: Gitlab::Regex.pypi_version_regex }, if: :pypi?
......@@ -120,6 +121,10 @@ class Packages::Package < ApplicationRecord
.where(packages_package_files: { file_name: file_name, file_sha256: sha256 }).last!
end
def self.by_name_and_version!(name, version)
find_by!(name: name, version: version)
end
def self.pluck_names
pluck(:name)
end
......
- page_title @group.name
- page_description @group.description unless page_description
- header_title group_title(@group) unless header_title
- page_description @group.description_html unless page_description
- header_title group_title(@group) unless header_title
- nav "group"
- display_subscription_banner!
- display_namespace_storage_limit_alert!
......
- page_title @project.full_name
- page_description @project.description unless page_description
- header_title project_title(@project) unless header_title
- page_description @project.description_html unless page_description
- header_title project_title(@project) unless header_title
- nav "project"
- display_subscription_banner!
- display_namespace_storage_limit_alert!
......
......@@ -2,7 +2,7 @@
- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")
- page_description @issue.description
- page_description @issue.description_html
- page_card_attributes @issue.card_attributes
- if @issue.relocation_target
- page_canonical_link @issue.relocation_target.present(current_user: current_user).web_url
......
......@@ -3,7 +3,7 @@
- add_to_breadcrumbs _("Merge Requests"), project_merge_requests_path(@project)
- breadcrumb_title @merge_request.to_reference
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", _("Merge Requests")
- page_description @merge_request.description
- page_description @merge_request.description_html
- page_card_attributes @merge_request.card_attributes
- suggest_changes_help_path = help_page_path('user/discussions/index.md', anchor: 'suggest-changes')
- number_of_pipelines = @pipelines.size
......
- add_to_breadcrumbs _('Milestones'), project_milestones_path(@project)
- breadcrumb_title @milestone.title
- page_title @milestone.title, _('Milestones')
- page_description @milestone.description
- page_description @milestone.description_html
- add_page_specific_style 'page_bundles/milestone'
= render 'shared/milestones/header', milestone: @milestone
......
......@@ -2,7 +2,7 @@
- @hide_breadcrumbs = true
- @no_container = true
- page_title @user.blocked? ? s_('UserProfile|Blocked user') : @user.name
- page_description @user.bio
- page_description @user.bio_html
- header_title @user.name, user_path(@user)
- link_classes = "flex-grow-1 mx-1 "
......
---
title: Strip markdown from og:description meta tags
merge_request: 42918
author:
type: added
---
title: Migrate deprecated button to GlButton in ingress_modsecurity_settings.vue
merge_request: 43717
author:
type: other
......@@ -154,7 +154,7 @@ commits to a feature branch in a remote repository in GitLab,
the CI/CD pipeline set for your project is triggered. By doing
so, GitLab CI/CD:
- Runs automated scripts (sequential or parallel) to:
- Runs automated scripts (sequentially or in parallel) to:
- Build and test your app.
- Preview the changes per merge request with Review Apps, as you
would see in your `localhost`.
......
......@@ -13,7 +13,7 @@
- breadcrumb_title epic_reference
- page_title "#{@epic.title} (#{epic_reference})", _("Epics")
- page_description @epic.description
- page_description @epic.description_html
- page_card_attributes @epic.card_attributes
......
......@@ -14,7 +14,7 @@ RSpec.describe 'Epic show', :js do
let_it_be(:markdown) do
<<-MARKDOWN.strip_heredoc
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
**Lorem** _ipsum_ dolor sit [amet](https://example.com), consectetur adipiscing elit.
Nos commodius agimus.
Ex rebus enim timiditas, non ex vocabulis nascitur.
Ita prorsus, inquam; Duo Reges: constructio interrete.
......@@ -116,6 +116,8 @@ RSpec.describe 'Epic show', :js do
end
describe 'Epic metadata' do
it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nos commodius agimus. Ex rebus enim timiditas, non ex vocabulis nascitur. Ita prorsus, inquam; Duo...'
it 'shows epic status, date and author in header' do
page.within('.epic-page-container .detail-page-header-body') do
expect(find('.issuable-status-box > span')).to have_content('Open')
......@@ -127,7 +129,7 @@ RSpec.describe 'Epic show', :js do
it 'shows epic title and description' do
page.within('.epic-page-container .detail-page-description') do
expect(find('.title-container .title')).to have_content(epic_title)
expect(find('.description .md')).to have_content(markdown.squish)
expect(find('.description .md')).to have_content('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nos commodius agimus. Ex rebus enim timiditas, non ex vocabulis nascitur. Ita prorsus, inquam; Duo Reges: constructio interrete.')
end
end
......
......@@ -30,7 +30,7 @@ module API
route_setting :authentication, job_token_allowed: true
params do
requires :package_name, type: String, desc: 'Package name'
requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true
requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true
end
......@@ -44,7 +44,7 @@ module API
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true
requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
......@@ -69,6 +69,29 @@ module API
forbidden!
end
desc 'Download package file' do
detail 'This feature was introduced in GitLab 13.5'
end
params do
requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true
requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true
end
route_setting :authentication, job_token_allowed: true
get do
authorize_read_package!(project)
package = ::Packages::Generic::PackageFinder.new(project).execute!(params[:package_name], params[:package_version])
package_file = ::Packages::PackageFileFinder.new(package, params[:file_name]).execute!
track_event('pull_package')
present_carrierwave_file!(package_file.file)
end
end
end
end
......
......@@ -142,9 +142,13 @@ module Gitlab
/\A\d+\.\d+\.\d+\z/
end
def generic_package_file_name_regex
def generic_package_name_regex
maven_file_name_regex
end
def generic_package_file_name_regex
generic_package_name_regex
end
end
extend self
......
......@@ -140,6 +140,14 @@ FactoryBot.define do
size { 1149.bytes }
end
trait(:generic) do
package
file_fixture { 'spec/fixtures/packages/generic/myfile.tar.gz' }
file_name { "#{package.name}.tar.gz" }
file_sha256 { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' }
size { 1149.bytes }
end
trait(:object_storage) do
file_store { Packages::PackageFileUploader::Store::REMOTE }
end
......
......@@ -184,4 +184,17 @@ RSpec.describe 'Group show page' do
expect(page).to have_selector('.notifications-btn.disabled', visible: true)
end
end
context 'page og:description' do
let(:group) { create(:group, description: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') }
let(:maintainer) { create(:user) }
before do
group.add_maintainer(maintainer)
sign_in(maintainer)
visit path
end
it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet'
end
end
......@@ -5,7 +5,7 @@ require "spec_helper"
RSpec.describe "User views issue" do
let_it_be(:project) { create(:project_empty_repo, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:issue) { create(:issue, project: project, description: "# Description header", author: user) }
let_it_be(:issue) { create(:issue, project: project, description: "# Description header\n\n**Lorem** _ipsum_ dolor sit [amet](https://example.com)", author: user) }
let_it_be(:note) { create(:note, noteable: issue, project: project, author: user) }
before_all do
......@@ -20,6 +20,8 @@ RSpec.describe "User views issue" do
it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") }
it_behaves_like 'page meta description', ' Description header Lorem ipsum dolor sit amet'
it 'shows the merge request and issue actions', :aggregate_failures do
expect(page).to have_link('New issue')
expect(page).to have_button('Create merge request')
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Merge request > User sees page metadata' do
let(:merge_request) { create(:merge_request, description: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') }
let(:project) { merge_request.target_project }
let(:user) { project.creator }
before do
project.add_maintainer(user)
sign_in(user)
visit project_merge_request_path(project, merge_request)
end
it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet'
end
......@@ -6,7 +6,7 @@ RSpec.describe "User views milestone" do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:milestone) { create(:milestone, project: project, description: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') }
let_it_be(:labels) { create_list(:label, 2, project: project) }
before_all do
......@@ -17,6 +17,14 @@ RSpec.describe "User views milestone" do
sign_in(user)
end
context 'page description' do
before do
visit(project_milestone_path(project, milestone))
end
it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet'
end
it "avoids N+1 database queries" do
issue_params = { project: project, assignees: [user], author: user, milestone: milestone, labels: labels }.freeze
......
......@@ -99,6 +99,15 @@ RSpec.describe 'Project' do
expect(page).to have_css('.home-panel-description .is-expanded')
end
end
context 'page description' do
before do
project.update_attribute(:description, '**Lorem** _ipsum_ dolor sit [amet](https://example.com)')
visit path
end
it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet'
end
end
describe 'project topics' do
......
......@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'User page' do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:user) { create(:user, bio: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)') }
context 'with public profile' do
it 'shows all the tabs' do
......@@ -174,4 +174,12 @@ RSpec.describe 'User page' do
end
end
end
context 'page description' do
before do
visit(user_path(user))
end
it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet'
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Packages::Generic::PackageFinder do
let_it_be(:project) { create(:project) }
let_it_be(:package) { create(:generic_package, project: project) }
describe '#execute!' do
subject(:finder) { described_class.new(project) }
it 'finds package by name and version' do
found_package = finder.execute!(package.name, package.version)
expect(found_package).to eq(package)
end
it 'ignores packages with same name but different version' do
create(:generic_package, project: project, name: package.name, version: '3.1.4')
found_package = finder.execute!(package.name, package.version)
expect(found_package).to eq(package)
end
it 'raises ActiveRecord::RecordNotFound if package is not found' do
expect { finder.execute!(package.name, '3.1.4') }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
......@@ -28,8 +28,10 @@ describe('IngressModsecuritySettings', () => {
});
};
const findSaveButton = () => wrapper.find('.btn-success');
const findCancelButton = () => wrapper.find('[variant="secondary"]');
const findSaveButton = () =>
wrapper.find('[data-qa-selector="save_ingress_modsecurity_settings"]');
const findCancelButton = () =>
wrapper.find('[data-qa-selector="cancel_ingress_modsecurity_settings"]');
const findModSecurityToggle = () => wrapper.find(GlToggle);
const findModSecurityDropdown = () => wrapper.find(GlDeprecatedDropdown);
......
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
import * as actions from '~/registry/settings/store/actions';
import * as types from '~/registry/settings/store/mutation_types';
describe('Actions Registry Store', () => {
describe.each`
actionName | mutationName | payload
${'setInitialState'} | ${types.SET_INITIAL_STATE} | ${'foo'}
${'updateSettings'} | ${types.UPDATE_SETTINGS} | ${'foo'}
${'toggleLoading'} | ${types.TOGGLE_LOADING} | ${undefined}
${'resetSettings'} | ${types.RESET_SETTINGS} | ${undefined}
`(
'$actionName invokes $mutationName with payload $payload',
({ actionName, mutationName, payload }) => {
it('should set state', done => {
testAction(actions[actionName], payload, {}, [{ type: mutationName, payload }], [], done);
});
},
);
describe('receiveSettingsSuccess', () => {
it('calls SET_SETTINGS', () => {
testAction(
actions.receiveSettingsSuccess,
'foo',
{},
[{ type: types.SET_SETTINGS, payload: 'foo' }],
[],
);
});
});
describe('fetchSettings', () => {
const state = {
projectId: 'bar',
};
const payload = {
data: {
container_expiration_policy: 'foo',
},
};
it('should fetch the data from the API', done => {
Api.project = jest.fn().mockResolvedValue(payload);
testAction(
actions.fetchSettings,
null,
state,
[],
[
{ type: 'toggleLoading' },
{ type: 'receiveSettingsSuccess', payload: payload.data.container_expiration_policy },
{ type: 'toggleLoading' },
],
done,
);
});
});
describe('saveSettings', () => {
const state = {
projectId: 'bar',
settings: 'baz',
};
const payload = {
data: {
tag_expiration_policies: 'foo',
},
};
it('should fetch the data from the API', done => {
Api.updateProject = jest.fn().mockResolvedValue(payload);
testAction(
actions.saveSettings,
null,
state,
[],
[
{ type: 'toggleLoading' },
{ type: 'receiveSettingsSuccess', payload: payload.data.container_expiration_policy },
{ type: 'toggleLoading' },
],
done,
);
});
});
});
import * as getters from '~/registry/settings/store/getters';
import * as utils from '~/registry/shared/utils';
import { formOptions } from '../../shared/mock_data';
describe('Getters registry settings store', () => {
const settings = {
enabled: true,
cadence: 'foo',
keep_n: 'bar',
older_than: 'baz',
name_regex: 'name-foo',
name_regex_keep: 'name-keep-bar',
};
describe.each`
getter | variable | formOption
${'getCadence'} | ${'cadence'} | ${'cadence'}
${'getKeepN'} | ${'keep_n'} | ${'keepN'}
${'getOlderThan'} | ${'older_than'} | ${'olderThan'}
`('Options getter', ({ getter, variable, formOption }) => {
beforeEach(() => {
utils.findDefaultOption = jest.fn();
});
it(`${getter} returns ${variable} when ${variable} exists in settings`, () => {
expect(getters[getter]({ settings })).toBe(settings[variable]);
});
it(`${getter} calls findDefaultOption when ${variable} does not exists in settings`, () => {
getters[getter]({ settings: {}, formOptions });
expect(utils.findDefaultOption).toHaveBeenCalledWith(formOptions[formOption]);
});
});
describe('getSettings', () => {
it('returns the content of settings', () => {
const computedGetters = {
getCadence: settings.cadence,
getOlderThan: settings.older_than,
getKeepN: settings.keep_n,
};
expect(getters.getSettings({ settings }, computedGetters)).toEqual(settings);
});
});
describe('getIsEdited', () => {
it('returns false when original is equal to settings', () => {
const same = { foo: 'bar' };
expect(getters.getIsEdited({ original: same, settings: same })).toBe(false);
});
it('returns true when original is different from settings', () => {
expect(getters.getIsEdited({ original: { foo: 'bar' }, settings: { foo: 'baz' } })).toBe(
true,
);
});
});
describe('getIsDisabled', () => {
it.each`
original | enableHistoricEntries | result
${undefined} | ${false} | ${true}
${{ foo: 'bar' }} | ${undefined} | ${false}
${{}} | ${false} | ${false}
`(
'returns $result when original is $original and enableHistoricEntries is $enableHistoricEntries',
({ original, enableHistoricEntries, result }) => {
expect(getters.getIsDisabled({ original, enableHistoricEntries })).toBe(result);
},
);
});
});
import mutations from '~/registry/settings/store/mutations';
import * as types from '~/registry/settings/store/mutation_types';
import createState from '~/registry/settings/store/state';
import { formOptions, stringifiedFormOptions } from '../../shared/mock_data';
describe('Mutations Registry Store', () => {
let mockState;
beforeEach(() => {
mockState = createState();
});
describe('SET_INITIAL_STATE', () => {
it('should set the initial state', () => {
const payload = {
projectId: 'foo',
enableHistoricEntries: false,
adminSettingsPath: 'foo',
isAdmin: true,
};
const expectedState = { ...mockState, ...payload, formOptions };
mutations[types.SET_INITIAL_STATE](mockState, {
...payload,
...stringifiedFormOptions,
});
expect(mockState).toEqual(expectedState);
});
});
describe('UPDATE_SETTINGS', () => {
it('should update the settings', () => {
mockState.settings = { foo: 'bar' };
const payload = { foo: 'baz' };
const expectedState = { ...mockState, settings: payload };
mutations[types.UPDATE_SETTINGS](mockState, { settings: payload });
expect(mockState.settings).toEqual(expectedState.settings);
});
});
describe('SET_SETTINGS', () => {
it('should set the settings and original', () => {
const payload = { foo: 'baz' };
const expectedState = { ...mockState, settings: payload };
mutations[types.SET_SETTINGS](mockState, payload);
expect(mockState.settings).toEqual(expectedState.settings);
expect(mockState.original).toEqual(expectedState.settings);
});
it('should keep the default state when settings is not present', () => {
const originalSettings = { ...mockState.settings };
mutations[types.SET_SETTINGS](mockState);
expect(mockState.settings).toEqual(originalSettings);
expect(mockState.original).toEqual(undefined);
});
});
describe('RESET_SETTINGS', () => {
it('should copy original over settings', () => {
mockState.settings = { foo: 'bar' };
mockState.original = { foo: 'baz' };
mutations[types.RESET_SETTINGS](mockState);
expect(mockState.settings).toEqual(mockState.original);
});
it('if original is undefined it should initialize to empty object', () => {
mockState.settings = { foo: 'bar' };
mockState.original = undefined;
mutations[types.RESET_SETTINGS](mockState);
expect(mockState.settings).toEqual({});
});
});
describe('TOGGLE_LOADING', () => {
it('should toggle the loading', () => {
mutations[types.TOGGLE_LOADING](mockState);
expect(mockState.isLoading).toEqual(true);
});
});
});
......@@ -599,6 +599,20 @@ RSpec.describe Gitlab::Regex do
it { is_expected.not_to match('') }
end
describe '.generic_package_name_regex' do
subject { described_class.generic_package_name_regex }
it { is_expected.to match('123') }
it { is_expected.to match('foo') }
it { is_expected.to match('foo.bar.baz-2.0-20190901.47283-1') }
it { is_expected.not_to match('../../foo') }
it { is_expected.not_to match('..\..\foo') }
it { is_expected.not_to match('%2f%2e%2e%2f%2essh%2fauthorized_keys') }
it { is_expected.not_to match('$foo/bar') }
it { is_expected.not_to match('my file name') }
it { is_expected.not_to match('!!()()') }
end
describe '.generic_package_file_name_regex' do
subject { described_class.generic_package_file_name_regex }
......
......@@ -108,6 +108,20 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value('.foobar').for(:name) }
it { is_expected.not_to allow_value('%foo%bar').for(:name) }
end
context 'generic package' do
subject { build_stubbed(:generic_package) }
it { is_expected.to allow_value('123').for(:name) }
it { is_expected.to allow_value('foo').for(:name) }
it { is_expected.to allow_value('foo.bar.baz-2.0-20190901.47283-1').for(:name) }
it { is_expected.not_to allow_value('../../foo').for(:name) }
it { is_expected.not_to allow_value('..\..\foo').for(:name) }
it { is_expected.not_to allow_value('%2f%2e%2e%2f%2essh%2fauthorized_keys').for(:name) }
it { is_expected.not_to allow_value('$foo/bar').for(:name) }
it { is_expected.not_to allow_value('my file name').for(:name) }
it { is_expected.not_to allow_value('!!().for(:name)().for(:name)').for(:name) }
end
end
describe '#version' do
......
This diff is collapsed.
# frozen_string_literal: true
RSpec.shared_examples 'page meta description' do |expected_description|
it 'renders the page with description, og:description, and twitter:description meta tags that contains a plain-text version of the markdown', :aggregate_failures do
%w(name='description' property='og:description' property='twitter:description').each do |selector|
expect(page).to have_selector("meta[#{selector}][content='#{expected_description}']", visible: false)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment