Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
cd54f7e81b
commit
58d68e313f
2
Gemfile
2
Gemfile
|
|
@ -413,7 +413,7 @@ group :test do
|
|||
|
||||
gem 'shoulda-matchers', '~> 4.0.1', require: false
|
||||
gem 'email_spec', '~> 2.2.0'
|
||||
gem 'webmock', '~> 3.5.1'
|
||||
gem 'webmock', '~> 3.9.1'
|
||||
gem 'rails-controller-testing'
|
||||
gem 'concurrent-ruby', '~> 1.1'
|
||||
gem 'test-prof', '~> 0.12.0'
|
||||
|
|
|
|||
|
|
@ -547,7 +547,7 @@ GEM
|
|||
tilt
|
||||
hana (1.3.6)
|
||||
hangouts-chat (0.0.5)
|
||||
hashdiff (0.3.8)
|
||||
hashdiff (1.0.1)
|
||||
hashie (3.6.0)
|
||||
hashie-forbidden_attributes (0.1.1)
|
||||
hashie (>= 3.0)
|
||||
|
|
@ -1214,10 +1214,10 @@ GEM
|
|||
webfinger (1.1.0)
|
||||
activesupport
|
||||
httpclient (>= 2.4)
|
||||
webmock (3.5.1)
|
||||
webmock (3.9.1)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
|
|
@ -1496,7 +1496,7 @@ DEPENDENCIES
|
|||
version_sorter (~> 2.2.4)
|
||||
vmstat (~> 2.3.0)
|
||||
webauthn (~> 2.3)
|
||||
webmock (~> 3.5.1)
|
||||
webmock (~> 3.9.1)
|
||||
wikicloth (= 0.8.1)
|
||||
yajl-ruby (~> 1.4.1)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import UsersSelect from './users_select';
|
|||
export default class IssuableContext {
|
||||
constructor(currentUser) {
|
||||
this.userSelect = new UsersSelect(currentUser);
|
||||
this.reviewersSelect = new UsersSelect(currentUser, '.js-reviewer-search');
|
||||
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,17 @@ export default class SidebarMediator {
|
|||
return this.service.update(field, data);
|
||||
}
|
||||
|
||||
saveReviewers(field) {
|
||||
const selected = this.store.reviewers.map(u => u.id);
|
||||
|
||||
// If there are no ids, that means we have to unassign (which is id = 0)
|
||||
// And it only accepts an array, hence [0]
|
||||
const reviewers = selected.length === 0 ? [0] : selected;
|
||||
const data = { reviewer_ids: reviewers };
|
||||
|
||||
return this.service.update(field, data);
|
||||
}
|
||||
|
||||
setMoveToProjectId(projectId) {
|
||||
this.store.setMoveToProjectId(projectId);
|
||||
}
|
||||
|
|
@ -55,6 +66,7 @@ export default class SidebarMediator {
|
|||
|
||||
processFetchedData(data) {
|
||||
this.store.setAssigneeData(data);
|
||||
this.store.setReviewerData(data);
|
||||
this.store.setTimeTrackingData(data);
|
||||
this.store.setParticipantsData(data);
|
||||
this.store.setSubscriptionsData(data);
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ export default class SidebarStore {
|
|||
this.humanTimeSpent = '';
|
||||
this.timeTrackingLimitToHours = timeTrackingLimitToHours;
|
||||
this.assignees = [];
|
||||
this.reviewers = [];
|
||||
this.isFetching = {
|
||||
assignees: true,
|
||||
reviewers: true,
|
||||
participants: true,
|
||||
subscriptions: true,
|
||||
};
|
||||
|
|
@ -42,6 +44,13 @@ export default class SidebarStore {
|
|||
}
|
||||
}
|
||||
|
||||
setReviewerData(data) {
|
||||
this.isFetching.reviewers = false;
|
||||
if (data.reviewers) {
|
||||
this.reviewers = data.reviewers;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeTrackingData(data) {
|
||||
this.timeEstimate = data.time_estimate;
|
||||
this.totalTimeSpent = data.total_time_spent;
|
||||
|
|
@ -75,20 +84,40 @@ export default class SidebarStore {
|
|||
}
|
||||
}
|
||||
|
||||
addReviewer(reviewer) {
|
||||
if (!this.findReviewer(reviewer)) {
|
||||
this.reviewers.push(reviewer);
|
||||
}
|
||||
}
|
||||
|
||||
findAssignee(findAssignee) {
|
||||
return this.assignees.find(assignee => assignee.id === findAssignee.id);
|
||||
}
|
||||
|
||||
findReviewer(findReviewer) {
|
||||
return this.reviewers.find(reviewer => reviewer.id === findReviewer.id);
|
||||
}
|
||||
|
||||
removeAssignee(removeAssignee) {
|
||||
if (removeAssignee) {
|
||||
this.assignees = this.assignees.filter(assignee => assignee.id !== removeAssignee.id);
|
||||
}
|
||||
}
|
||||
|
||||
removeReviewer(removeReviewer) {
|
||||
if (removeReviewer) {
|
||||
this.reviewers = this.reviewers.filter(reviewer => reviewer.id !== removeReviewer.id);
|
||||
}
|
||||
}
|
||||
|
||||
removeAllAssignees() {
|
||||
this.assignees = [];
|
||||
}
|
||||
|
||||
removeAllReviewers() {
|
||||
this.reviewers = [];
|
||||
}
|
||||
|
||||
setAssigneesFromRealtime(data) {
|
||||
this.assignees = data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
|
|||
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
|
||||
|
||||
function UsersSelect(currentUser, els, options = {}) {
|
||||
const elsClassName = els?.toString().match('.(.+$)')[1];
|
||||
const $els = $(els || '.js-user-search');
|
||||
this.users = this.users.bind(this);
|
||||
this.user = this.user.bind(this);
|
||||
|
|
@ -127,9 +128,16 @@ function UsersSelect(currentUser, els, options = {}) {
|
|||
.find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`);
|
||||
|
||||
firstSelected.remove();
|
||||
emitSidebarEvent('sidebar.removeAssignee', {
|
||||
id: firstSelectedId,
|
||||
});
|
||||
|
||||
if ($dropdown.hasClass(elsClassName)) {
|
||||
emitSidebarEvent('sidebar.removeReviewer', {
|
||||
id: firstSelectedId,
|
||||
});
|
||||
} else {
|
||||
emitSidebarEvent('sidebar.removeAssignee', {
|
||||
id: firstSelectedId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -392,7 +400,11 @@ function UsersSelect(currentUser, els, options = {}) {
|
|||
defaultLabel,
|
||||
hidden() {
|
||||
if ($dropdown.hasClass('js-multiselect')) {
|
||||
emitSidebarEvent('sidebar.saveAssignees');
|
||||
if ($dropdown.hasClass(elsClassName)) {
|
||||
emitSidebarEvent('sidebar.saveReviewers');
|
||||
} else {
|
||||
emitSidebarEvent('sidebar.saveAssignees');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$dropdown.data('alwaysShowSelectbox')) {
|
||||
|
|
@ -428,10 +440,18 @@ function UsersSelect(currentUser, els, options = {}) {
|
|||
previouslySelected.each((index, element) => {
|
||||
element.remove();
|
||||
});
|
||||
emitSidebarEvent('sidebar.removeAllAssignees');
|
||||
if ($dropdown.hasClass(elsClassName)) {
|
||||
emitSidebarEvent('sidebar.removeAllReviewers');
|
||||
} else {
|
||||
emitSidebarEvent('sidebar.removeAllAssignees');
|
||||
}
|
||||
} else if (isActive) {
|
||||
// user selected
|
||||
emitSidebarEvent('sidebar.addAssignee', user);
|
||||
if ($dropdown.hasClass(elsClassName)) {
|
||||
emitSidebarEvent('sidebar.addReviewer', user);
|
||||
} else {
|
||||
emitSidebarEvent('sidebar.addAssignee', user);
|
||||
}
|
||||
|
||||
// Remove unassigned selection (if it was previously selected)
|
||||
const unassignedSelected = $dropdown
|
||||
|
|
@ -448,7 +468,11 @@ function UsersSelect(currentUser, els, options = {}) {
|
|||
}
|
||||
|
||||
// User unselected
|
||||
emitSidebarEvent('sidebar.removeAssignee', user);
|
||||
if ($dropdown.hasClass(elsClassName)) {
|
||||
emitSidebarEvent('sidebar.removeReviewer', user);
|
||||
} else {
|
||||
emitSidebarEvent('sidebar.removeAssignee', user);
|
||||
}
|
||||
}
|
||||
|
||||
if (getSelected().find(u => u === gon.current_user_id)) {
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ export default {
|
|||
<template>
|
||||
<div class="branch-commit cgray">
|
||||
<template v-if="shouldShowRefInfo">
|
||||
<div class="icon-container">
|
||||
<div class="icon-container gl-display-inline-block">
|
||||
<gl-icon v-if="tag" name="tag" />
|
||||
<gl-icon v-else-if="mergeRequestRef" name="git-merge" />
|
||||
<gl-icon v-else name="branch" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
name: 'MemberSource',
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
memberSource: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isDirectMember: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="isDirectMember">{{ __('Direct member') }}</span>
|
||||
<a v-else v-gl-tooltip.hover :title="__('Inherited')" :href="memberSource.webUrl">{{
|
||||
memberSource.name
|
||||
}}</a>
|
||||
</template>
|
||||
|
|
@ -4,6 +4,7 @@ import { GlTable } from '@gitlab/ui';
|
|||
import { FIELDS } from '../constants';
|
||||
import initUserPopovers from '~/user_popovers';
|
||||
import MemberAvatar from './member_avatar.vue';
|
||||
import MemberSource from './member_source.vue';
|
||||
import MembersTableCell from './members_table_cell.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -12,6 +13,7 @@ export default {
|
|||
GlTable,
|
||||
MemberAvatar,
|
||||
MembersTableCell,
|
||||
MemberSource,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['members', 'tableFields']),
|
||||
|
|
@ -43,8 +45,10 @@ export default {
|
|||
</members-table-cell>
|
||||
</template>
|
||||
|
||||
<template #cell(source)>
|
||||
<!-- Temporarily empty -->
|
||||
<template #cell(source)="{ item: member }">
|
||||
<members-table-cell #default="{ isDirectMember }" :member="member">
|
||||
<member-source :is-direct-member="isDirectMember" :member-source="member.source" />
|
||||
</members-table-cell>
|
||||
</template>
|
||||
|
||||
<template #head(actions)="{ label }">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import { MEMBER_TYPES } from '../constants';
|
||||
|
||||
export default {
|
||||
|
|
@ -10,6 +11,7 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['sourceId']),
|
||||
isGroup() {
|
||||
return Boolean(this.member.sharedWithGroup);
|
||||
},
|
||||
|
|
@ -30,10 +32,14 @@ export default {
|
|||
|
||||
return MEMBER_TYPES.user;
|
||||
},
|
||||
isDirectMember() {
|
||||
return this.member.source?.id === this.sourceId;
|
||||
},
|
||||
},
|
||||
render() {
|
||||
return this.$scopedSlots.default({
|
||||
memberType: this.memberType,
|
||||
isDirectMember: this.isDirectMember,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -111,6 +111,10 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.pipeline-tags .label-container {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,22 +128,6 @@
|
|||
}
|
||||
|
||||
.ci-table {
|
||||
.build.retried {
|
||||
background-color: $gray-lightest;
|
||||
}
|
||||
|
||||
.commit-link {
|
||||
a {
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-left: 0;
|
||||
float: none;
|
||||
|
|
@ -191,45 +179,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: inline-block;
|
||||
|
||||
&.commit-icon {
|
||||
width: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play button with icon in dropdowns
|
||||
*/
|
||||
.no-btn {
|
||||
border: 0;
|
||||
background: none;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
|
||||
.icon-play {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-right: 5px;
|
||||
height: 13px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.duration,
|
||||
.finished-at {
|
||||
color: $gl-text-color-secondary;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
.fa {
|
||||
font-size: 12px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
|
@ -241,14 +196,6 @@
|
|||
.build-link a {
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
||||
.btn-group.open .dropdown-toggle {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.pipeline-tags .label-container {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.stage-cell {
|
||||
|
|
|
|||
|
|
@ -386,6 +386,12 @@ module IssuablesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def reviewer_sidebar_data(reviewer, merge_request: nil)
|
||||
{ avatar_url: reviewer.avatar_url, name: reviewer.name, username: reviewer.username }.tap do |data|
|
||||
data[:can_merge] = merge_request.can_be_merged_by?(reviewer) if merge_request
|
||||
end
|
||||
end
|
||||
|
||||
def issuable_squash_option?(issuable, project)
|
||||
if issuable.persisted?
|
||||
issuable.squash
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
%span.build-link ##{artifact.job_id}
|
||||
|
||||
- if artifact.job.ref
|
||||
.icon-container{ "aria-label" => artifact.job.tag? ? _('Tag') : _('Branch') }
|
||||
.icon-container.gl-display-inline-block{ "aria-label" => artifact.job.tag? ? _('Tag') : _('Branch') }
|
||||
= artifact.job.tag? ? sprite_icon('tag', css_class: 'sprite') : sprite_icon('branch', css_class: 'sprite')
|
||||
= link_to artifact.job.ref, project_ref_path(@project, artifact.job.ref), class: 'ref-name'
|
||||
- else
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3
|
||||
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
|
||||
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn' do
|
||||
= sprite_icon('fork', { css_class: 'icon' })
|
||||
= sprite_icon('fork', css_class: 'icon')
|
||||
%span= s_('ProjectOverview|Fork')
|
||||
- else
|
||||
- can_create_fork = current_user.can?(:create_fork)
|
||||
= link_to new_project_fork_path(@project),
|
||||
class: "btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center fork-btn #{'has-tooltip disabled' unless can_create_fork}",
|
||||
title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do
|
||||
= sprite_icon('fork', { css_class: 'icon' })
|
||||
= sprite_icon('fork', css_class: 'icon')
|
||||
%span= s_('ProjectOverview|Fork')
|
||||
%span.fork-count.count-badge-count.d-flex.align-items-center
|
||||
= link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3
|
||||
%button.count-badge-button.btn.btn-default.btn-xs.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }
|
||||
- if current_user.starred?(@project)
|
||||
= sprite_icon('star', { css_class: 'icon' })
|
||||
= sprite_icon('star', css_class: 'icon')
|
||||
%span.starred= s_('ProjectOverview|Unstar')
|
||||
- else
|
||||
= sprite_icon('star-o', { css_class: 'icon' })
|
||||
= sprite_icon('star-o', css_class: 'icon')
|
||||
%span= s_('ProjectOverview|Star')
|
||||
%span.star-count.count-badge-count.d-flex.align-items-center
|
||||
= link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
- else
|
||||
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3
|
||||
= link_to new_user_session_path, class: 'btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
|
||||
= sprite_icon('star-o', { css_class: 'icon' })
|
||||
= sprite_icon('star-o', css_class: 'icon')
|
||||
%span= s_('ProjectOverview|Star')
|
||||
%span.star-count.count-badge-count.d-flex.align-items-center
|
||||
= link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
- if ref
|
||||
- if job.ref
|
||||
.icon-container
|
||||
.icon-container.gl-display-inline-block
|
||||
= job.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
|
||||
= link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name"
|
||||
- else
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.table-mobile-content
|
||||
.branch-commit.cgray
|
||||
- if deployment.ref
|
||||
%span.icon-container
|
||||
%span.icon-container.gl-display-inline-block
|
||||
= deployment.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
|
||||
= link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
|
||||
.icon-container.commit-icon
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@
|
|||
.loading.hide
|
||||
.spinner.spinner-md
|
||||
|
||||
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, source_branch: @merge_request.source_branch
|
||||
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, reviewers: @merge_request.reviewers, source_branch: @merge_request.source_branch
|
||||
|
||||
- if @merge_request.can_be_reverted?(current_user)
|
||||
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
- issuable_type = issuable_sidebar[:type]
|
||||
- signed_in = !!issuable_sidebar.dig(:current_user, :id)
|
||||
|
||||
#js-vue-sidebar-reviewers{ data: { field: issuable_type, signed_in: signed_in } }
|
||||
.title.hide-collapsed
|
||||
= _('Reviewer')
|
||||
= loading_icon(css_class: 'gl-vertical-align-text-bottom')
|
||||
|
||||
.selectbox.hide-collapsed
|
||||
- if reviewers.none?
|
||||
= hidden_field_tag "#{issuable_type}[reviewer_ids][]", 0, id: nil
|
||||
- else
|
||||
- reviewers.each do |reviewer|
|
||||
= hidden_field_tag "#{issuable_type}[reviewer_ids][]", reviewer.id, id: nil, data: reviewer_sidebar_data(reviewer, merge_request: @merge_request)
|
||||
|
||||
- options = { toggle_class: 'js-reviewer-search js-author-search',
|
||||
title: _('Request review from'),
|
||||
filter: true,
|
||||
dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author',
|
||||
placeholder: _('Search users'),
|
||||
data: { first_user: issuable_sidebar.dig(:current_user, :username),
|
||||
current_user: true,
|
||||
iid: issuable_sidebar[:iid],
|
||||
issuable_type: issuable_type,
|
||||
project_id: issuable_sidebar[:project_id],
|
||||
author_id: issuable_sidebar[:author_id],
|
||||
field_name: "#{issuable_type}[reviewer_ids][]",
|
||||
issue_update: issuable_sidebar[:issuable_json_path],
|
||||
ability_name: issuable_type,
|
||||
null_user: true,
|
||||
display: 'static' } }
|
||||
|
||||
- dropdown_options = reviewers_dropdown_options(issuable_type)
|
||||
- title = dropdown_options[:title]
|
||||
- options[:toggle_class] += ' js-multiselect js-save-user-data'
|
||||
- data = { field_name: "#{issuable_type}[reviewer_ids][]" }
|
||||
- data[:multi_select] = true
|
||||
- data['dropdown-title'] = title
|
||||
- data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
|
||||
- data['max-select'] = dropdown_options[:data][:'max-select'] if dropdown_options[:data][:'max-select']
|
||||
- options[:data].merge!(data)
|
||||
|
||||
- if experiment_enabled?(:invite_members_version_a) && can_import_members?
|
||||
- options[:dropdown_class] += ' dropdown-extended-height'
|
||||
- options[:footer_content] = true
|
||||
- options[:wrapper_class] = 'js-sidebar-reviewer-dropdown'
|
||||
|
||||
= dropdown_tag(title, options: options) do
|
||||
%ul.dropdown-footer-list
|
||||
%li
|
||||
= link_to _('Invite Members'),
|
||||
project_project_members_path(@project),
|
||||
title: _('Invite Members'),
|
||||
data: { 'is-link': true, 'track-event': 'click_invite_members', 'track-label': 'edit_reviewer' }
|
||||
- else
|
||||
= dropdown_tag(title, options: options)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: broadcast_issue_updates
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30732
|
||||
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1210
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: dashboard_pipeline_status
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22029
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/209061
|
||||
group: group::continuous integration
|
||||
type: development
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: real_time_issue_sidebar
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30239
|
||||
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1210
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
|||
|
|
@ -647,7 +647,7 @@ tables: `namespaces`. This can be translated to:
|
|||
```sql
|
||||
ALTER TABLE namespaces
|
||||
ALTER COLUMN request_access_enabled
|
||||
DEFAULT false
|
||||
SET DEFAULT false
|
||||
```
|
||||
|
||||
In this particular case, the default value exists and we're just changing the metadata for
|
||||
|
|
|
|||
|
|
@ -674,11 +674,6 @@ To delete an existing site profile:
|
|||
## Scanner profile
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222767) in GitLab 13.4.
|
||||
> - [Deployed behind a feature flag](../../feature_flags.md), enabled by default.
|
||||
> - Enabled on GitLab.com.
|
||||
> - Can be enabled or disabled per-project.
|
||||
> - Recommended for production use.
|
||||
> - For GitLab self-managed instances, GitLab administrators can [disable this feature](#enable-or-disable-dast-scanner-profiles).
|
||||
|
||||
A scanner profile defines the scanner settings used to run an on-demand scan:
|
||||
|
||||
|
|
@ -713,29 +708,6 @@ To delete a scanner profile:
|
|||
1. Click **Manage** in the **DAST Profiles** row.
|
||||
1. Click **{remove}** in the scanner profile's row.
|
||||
|
||||
### Enable or disable DAST scanner profiles
|
||||
|
||||
The scanner profile feature is ready for production use. It's deployed behind a feature flag that
|
||||
is **enabled by default**. [GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can opt to disable it.
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
# Instance-wide
|
||||
Feature.disable(:security_on_demand_scans_scanner_profiles)
|
||||
# or by project
|
||||
Feature.disable(:security_on_demand_scans_scanner_profiles, Project.find(<project id>))
|
||||
```
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
# Instance-wide
|
||||
Feature.enable(:security_on_demand_scans_scanner_profiles)
|
||||
# or by project
|
||||
Feature.enable(:security_on_demand_scans_scanner_profiles, Project.find(<project ID>))
|
||||
```
|
||||
|
||||
## On-demand scans
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218465) in GitLab 13.2.
|
||||
|
|
|
|||
|
|
@ -8934,6 +8934,9 @@ msgstr ""
|
|||
msgid "Diffs|Something went wrong while fetching diff lines."
|
||||
msgstr ""
|
||||
|
||||
msgid "Direct member"
|
||||
msgstr ""
|
||||
|
||||
msgid "Direction"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13614,6 +13617,9 @@ msgstr ""
|
|||
msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Inherited"
|
||||
msgstr ""
|
||||
|
||||
msgid "Inherited:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17673,12 +17679,6 @@ msgstr ""
|
|||
msgid "On track"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Attached branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Attached branch is where the scan job runs."
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17715,18 +17715,9 @@ msgstr ""
|
|||
msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Only a passive scan can be performed on demand."
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Passive"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Run scan"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Scan mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Scanner profile"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21668,6 +21659,9 @@ msgstr ""
|
|||
msgid "Request parameter %{param} is missing."
|
||||
msgstr ""
|
||||
|
||||
msgid "Request review from"
|
||||
msgstr ""
|
||||
|
||||
msgid "Request to link SAML account must be authorized"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21938,6 +21932,9 @@ msgstr ""
|
|||
msgid "ReviewApp|Enable Review App"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reviewer"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reviewing"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,168 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects > Wiki > User previews markdown changes', :js do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
|
||||
let(:wiki_page) { create(:wiki_page, wiki: project.wiki, title: 'home', content: '[some link](other-page)') }
|
||||
let(:wiki_content) do
|
||||
<<-HEREDOC
|
||||
Some text so key event for [ does not trigger an incorrect replacement.
|
||||
[regular link](regular)
|
||||
[relative link 1](../relative)
|
||||
[relative link 2](./relative)
|
||||
[relative link 3](./e/f/relative)
|
||||
[spaced link](title with spaces)
|
||||
HEREDOC
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context "while creating a new wiki page" do
|
||||
context "when there are no spaces or hyphens in the page name" do
|
||||
it "rewrites relative links as expected" do
|
||||
create_wiki_page('a/b/c/d', content: wiki_content)
|
||||
|
||||
expect(page).to have_content("regular link")
|
||||
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/relative\">relative link 1</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/c/relative\">relative link 2</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/c/e/f/relative\">relative link 3</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>")
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are spaces in the page name" do
|
||||
it "rewrites relative links as expected" do
|
||||
create_wiki_page('a page/b page/c page/d page', content: wiki_content)
|
||||
|
||||
expect(page).to have_content("regular link")
|
||||
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/relative\">relative link 1</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>")
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are hyphens in the page name" do
|
||||
it "rewrites relative links as expected" do
|
||||
create_wiki_page('a-page/b-page/c-page/d-page', content: wiki_content)
|
||||
|
||||
expect(page).to have_content("regular link")
|
||||
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/relative\">relative link 1</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "while editing a wiki page" do
|
||||
context "when there are no spaces or hyphens in the page name" do
|
||||
it "rewrites relative links as expected" do
|
||||
create_wiki_page('a/b/c/d')
|
||||
click_link 'Edit'
|
||||
|
||||
fill_in :wiki_content, with: wiki_content
|
||||
click_on "Preview"
|
||||
|
||||
expect(page).to have_content("regular link")
|
||||
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/relative\">relative link 1</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/c/relative\">relative link 2</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a/b/c/e/f/relative\">relative link 3</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>")
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are spaces in the page name" do
|
||||
it "rewrites relative links as expected" do
|
||||
create_wiki_page('a page/b page/c page/d page')
|
||||
click_link 'Edit'
|
||||
|
||||
fill_in :wiki_content, with: wiki_content
|
||||
click_on "Preview"
|
||||
|
||||
expect(page).to have_content("regular link")
|
||||
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/relative\">relative link 1</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>")
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are hyphens in the page name" do
|
||||
it "rewrites relative links as expected" do
|
||||
create_wiki_page('a-page/b-page/c-page/d-page')
|
||||
click_link 'Edit'
|
||||
|
||||
fill_in :wiki_content, with: wiki_content
|
||||
click_on "Preview"
|
||||
|
||||
expect(page).to have_content("regular link")
|
||||
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/regular\">regular link</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/relative\">relative link 1</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
|
||||
expect(page.html).to include("<a href=\"/#{project.full_path}/-/wikis/title%20with%20spaces\">spaced link</a>")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when rendering the preview' do
|
||||
it 'renders content with CommonMark' do
|
||||
create_wiki_page('a-page/b-page/c-page/common-mark')
|
||||
click_link 'Edit'
|
||||
|
||||
fill_in :wiki_content, with: "1. one\n - sublist\n"
|
||||
click_on "Preview"
|
||||
|
||||
# the above generates two separate lists (not embedded) in CommonMark
|
||||
expect(page).to have_content("sublist")
|
||||
expect(page).not_to have_xpath("//ol//li//ul")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "does not linkify double brackets inside code blocks as expected" do
|
||||
wiki_content = <<-HEREDOC
|
||||
`[[do_not_linkify]]`
|
||||
```
|
||||
[[also_do_not_linkify]]
|
||||
```
|
||||
HEREDOC
|
||||
|
||||
create_wiki_page('linkify_test', wiki_content)
|
||||
|
||||
expect(page).to have_content("do_not_linkify")
|
||||
|
||||
expect(page.html).to include('[[do_not_linkify]]')
|
||||
expect(page.html).to include('[[also_do_not_linkify]]')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_wiki_page(path, content = 'content')
|
||||
visit project_wiki_path(project, wiki_page)
|
||||
|
||||
click_link 'New page'
|
||||
|
||||
fill_in :wiki_title, with: path
|
||||
fill_in :wiki_content, with: content
|
||||
|
||||
click_button 'Create page'
|
||||
end
|
||||
end
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Wiki shortcuts', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
|
||||
let(:wiki_page) { create(:wiki_page, wiki: project.wiki, title: 'home', content: 'Home page') }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
visit project_wiki_path(project, wiki_page)
|
||||
end
|
||||
|
||||
it 'Visit edit wiki page using "e" keyboard shortcut' do
|
||||
find('body').native.send_key('e')
|
||||
|
||||
expect(find('.wiki-page-title')).to have_content('Edit Page')
|
||||
end
|
||||
end
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe "User creates wiki page" do
|
||||
include WikiHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:wiki) { ProjectWiki.new(project, user) }
|
||||
let(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context "when wiki is empty" do
|
||||
before do |example|
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
wait_for_svg_to_be_loaded(example)
|
||||
|
||||
click_link "Create your first page"
|
||||
end
|
||||
|
||||
context "in a user namespace" do
|
||||
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
|
||||
|
||||
it "shows validation error message" do
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_content, with: "")
|
||||
|
||||
click_on("Create page")
|
||||
end
|
||||
|
||||
expect(page).to have_content("The form contains the following error:").and have_content("Content can't be blank")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_content, with: "[link test](test)")
|
||||
|
||||
click_on("Create page")
|
||||
end
|
||||
|
||||
expect(page).to have_content("Home").and have_content("link test")
|
||||
|
||||
click_link("link test")
|
||||
|
||||
expect(page).to have_content("Create New Page")
|
||||
end
|
||||
|
||||
it "shows non-escaped link in the pages list" do
|
||||
fill_in(:wiki_title, with: "one/two/three-test")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_content, with: "wiki content")
|
||||
|
||||
click_on("Create page")
|
||||
end
|
||||
|
||||
expect(current_path).to include("one/two/three-test")
|
||||
expect(page).to have_xpath("//a[@href='/#{project.full_path}/-/wikis/one/two/three-test']")
|
||||
end
|
||||
|
||||
it "has `Create home` as a commit message", :js do
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_field("wiki[message]", with: "Create home")
|
||||
end
|
||||
|
||||
it "creates a page from the home page" do
|
||||
fill_in(:wiki_content, with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n# Wiki header\n")
|
||||
fill_in(:wiki_message, with: "Adding links to wiki")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
click_button("Create page")
|
||||
end
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, "home"))
|
||||
expect(page).to have_content("test GitLab API doc Rake tasks Wiki header")
|
||||
.and have_content("Home")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_header_with_correct_id_and_link(1, "Wiki header", "wiki-header")
|
||||
|
||||
click_link("test")
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, "test"))
|
||||
|
||||
page.within(:css, ".nav-text") do
|
||||
expect(page).to have_content("Create New Page")
|
||||
end
|
||||
|
||||
click_link("Home")
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, "home"))
|
||||
|
||||
click_link("GitLab API")
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, "api"))
|
||||
|
||||
page.within(:css, ".nav-text") do
|
||||
expect(page).to have_content("Create")
|
||||
end
|
||||
|
||||
click_link("Home")
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, "home"))
|
||||
|
||||
click_link("Rake tasks")
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, "raketasks"))
|
||||
|
||||
page.within(:css, ".nav-text") do
|
||||
expect(page).to have_content("Create")
|
||||
end
|
||||
end
|
||||
|
||||
it "creates ASCII wiki with LaTeX blocks", :js do
|
||||
stub_application_setting(plantuml_url: "http://localhost", plantuml_enabled: true)
|
||||
|
||||
ascii_content = <<~MD
|
||||
:stem: latexmath
|
||||
|
||||
[stem]
|
||||
++++
|
||||
\\sqrt{4} = 2
|
||||
++++
|
||||
|
||||
another part
|
||||
|
||||
[latexmath]
|
||||
++++
|
||||
\\beta_x \\gamma
|
||||
++++
|
||||
|
||||
stem:[2+2] is 4
|
||||
MD
|
||||
|
||||
find("#wiki_format option[value=asciidoc]").select_option
|
||||
|
||||
fill_in(:wiki_content, with: ascii_content)
|
||||
|
||||
page.within(".wiki-form") do
|
||||
click_button("Create page")
|
||||
end
|
||||
|
||||
page.within ".md" do
|
||||
expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4")
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates a wiki page with Org markup', :aggregate_failures do
|
||||
org_content = <<~ORG
|
||||
* Heading
|
||||
** Subheading
|
||||
[[home][Link to Home]]
|
||||
ORG
|
||||
|
||||
page.within('.wiki-form') do
|
||||
find('#wiki_format option[value=org]').select_option
|
||||
fill_in(:wiki_content, with: org_content)
|
||||
click_button('Create page')
|
||||
end
|
||||
|
||||
expect(page).to have_selector('h1', text: 'Heading')
|
||||
expect(page).to have_selector('h2', text: 'Subheading')
|
||||
expect(page).to have_link('Link to Home', href: "/#{project.full_path}/-/wikis/home")
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki file attachments'
|
||||
end
|
||||
|
||||
context "in a group namespace", :js do
|
||||
let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
|
||||
|
||||
it "has `Create home` as a commit message" do
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_field("wiki[message]", with: "Create home")
|
||||
end
|
||||
|
||||
it "creates a page from the home page" do
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
|
||||
click_button("Create page")
|
||||
end
|
||||
|
||||
expect(page).to have_content("Home")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_content("My awesome wiki!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when wiki is not empty", :js do
|
||||
before do
|
||||
create(:wiki_page, wiki: wiki, title: 'home', content: 'Home page')
|
||||
|
||||
visit(project_wikis_path(project))
|
||||
end
|
||||
|
||||
context "in a user namespace" do
|
||||
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
|
||||
|
||||
context "via the `new wiki page` page" do
|
||||
it "creates a page with a single word" do
|
||||
click_link("New page")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_title, with: "foo")
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
end
|
||||
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field("wiki[message]", with: "Create foo")
|
||||
|
||||
click_button("Create page")
|
||||
|
||||
expect(page).to have_content("foo")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_content("My awesome wiki!")
|
||||
end
|
||||
|
||||
it "creates a page with spaces in the name" do
|
||||
click_link("New page")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_title, with: "Spaces in the name")
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
end
|
||||
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field("wiki[message]", with: "Create Spaces in the name")
|
||||
|
||||
click_button("Create page")
|
||||
|
||||
expect(page).to have_content("Spaces in the name")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_content("My awesome wiki!")
|
||||
end
|
||||
|
||||
it "creates a page with hyphens in the name" do
|
||||
click_link("New page")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_title, with: "hyphens-in-the-name")
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
end
|
||||
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field("wiki[message]", with: "Create hyphens in the name")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
|
||||
click_button("Create page")
|
||||
end
|
||||
|
||||
expect(page).to have_content("hyphens in the name")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_content("My awesome wiki!")
|
||||
end
|
||||
end
|
||||
|
||||
it "shows the emoji autocompletion dropdown" do
|
||||
click_link("New page")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
find("#wiki_content").native.send_keys("")
|
||||
|
||||
fill_in(:wiki_content, with: ":")
|
||||
end
|
||||
|
||||
expect(page).to have_selector(".atwho-view")
|
||||
end
|
||||
end
|
||||
|
||||
context "in a group namespace" do
|
||||
let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
|
||||
|
||||
context "via the `new wiki page` page" do
|
||||
it "creates a page" do
|
||||
click_link("New page")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_title, with: "foo")
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
end
|
||||
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field("wiki[message]", with: "Create foo")
|
||||
|
||||
click_button("Create page")
|
||||
|
||||
expect(page).to have_content("foo")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_content("My awesome wiki!")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sidebar feature' do
|
||||
context 'when there are some existing pages' do
|
||||
before do
|
||||
create(:wiki_page, wiki: wiki, title: 'home', content: 'home')
|
||||
create(:wiki_page, wiki: wiki, title: 'another', content: 'another')
|
||||
end
|
||||
|
||||
it 'renders a default sidebar when there is no customized sidebar' do
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
expect(page).to have_content('another')
|
||||
expect(page).not_to have_link('View All Pages')
|
||||
end
|
||||
|
||||
context 'when there is a customized sidebar' do
|
||||
before do
|
||||
create(:wiki_page, wiki: wiki, title: '_sidebar', content: 'My customized sidebar')
|
||||
end
|
||||
|
||||
it 'renders my customized sidebar instead of the default one' do
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
expect(page).to have_content('My customized sidebar')
|
||||
expect(page).not_to have_content('Another')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are 15 existing pages' do
|
||||
before do
|
||||
(1..5).each { |i| create(:wiki_page, wiki: wiki, title: "my page #{i}") }
|
||||
(6..10).each { |i| create(:wiki_page, wiki: wiki, title: "parent/my page #{i}") }
|
||||
(11..15).each { |i| create(:wiki_page, wiki: wiki, title: "grandparent/parent/my page #{i}") }
|
||||
end
|
||||
|
||||
it 'shows all pages in the sidebar' do
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
(1..15).each { |i| expect(page).to have_content("my page #{i}") }
|
||||
expect(page).not_to have_link('View All Pages')
|
||||
end
|
||||
|
||||
context 'when there are more than 15 existing pages' do
|
||||
before do
|
||||
create(:wiki_page, wiki: wiki, title: 'my page 16')
|
||||
end
|
||||
|
||||
it 'shows the first 15 pages in the sidebar' do
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
expect(page).to have_text('my page', count: 15)
|
||||
expect(page).to have_link('View All Pages')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User deletes wiki page', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
|
||||
let(:wiki_page) { create(:wiki_page, wiki: project.wiki) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
visit(project_wiki_path(project, wiki_page))
|
||||
end
|
||||
|
||||
it 'deletes a page' do
|
||||
click_on('Edit')
|
||||
click_on('Delete')
|
||||
find('.modal-footer .btn-danger').click
|
||||
|
||||
expect(page).to have_content('Page was successfully deleted')
|
||||
end
|
||||
end
|
||||
|
|
@ -1,263 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User updates wiki page' do
|
||||
include WikiHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when wiki is empty' do
|
||||
before do |example|
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
wait_for_svg_to_be_loaded(example)
|
||||
|
||||
click_link "Create your first page"
|
||||
end
|
||||
|
||||
context 'in a user namespace' do
|
||||
let(:project) { create(:project, :wiki_repo) }
|
||||
|
||||
it 'redirects back to the home edit page' do
|
||||
page.within(:css, '.wiki-form .form-actions') do
|
||||
click_on('Cancel')
|
||||
end
|
||||
|
||||
expect(current_path).to eq wiki_path(project.wiki)
|
||||
end
|
||||
|
||||
it 'updates a page that has a path', :js do
|
||||
fill_in(:wiki_title, with: 'one/two/three-test')
|
||||
|
||||
page.within '.wiki-form' do
|
||||
fill_in(:wiki_content, with: 'wiki content')
|
||||
click_on('Create page')
|
||||
end
|
||||
|
||||
expect(current_path).to include('one/two/three-test')
|
||||
expect(find('.wiki-pages')).to have_content('three')
|
||||
|
||||
first(:link, text: 'three').click
|
||||
|
||||
expect(find('.nav-text')).to have_content('three')
|
||||
|
||||
click_on('Edit')
|
||||
|
||||
expect(current_path).to include('one/two/three-test')
|
||||
expect(page).to have_content('Edit Page')
|
||||
|
||||
fill_in('Content', with: 'Updated Wiki Content')
|
||||
click_on('Save changes')
|
||||
|
||||
expect(page).to have_content('Updated Wiki Content')
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki file attachments'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when wiki is not empty' do
|
||||
let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) }
|
||||
let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, title: 'home', content: 'Home page') }
|
||||
|
||||
before do
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
click_link('Edit')
|
||||
end
|
||||
|
||||
context 'in a user namespace' do
|
||||
let(:project) { create(:project, :wiki_repo) }
|
||||
|
||||
it 'updates a page', :js do
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field('wiki[message]', with: 'Update home')
|
||||
|
||||
fill_in(:wiki_content, with: 'My awesome wiki!')
|
||||
click_button('Save changes')
|
||||
|
||||
expect(page).to have_content('Home')
|
||||
expect(page).to have_content("Last edited by #{user.name}")
|
||||
expect(page).to have_content('My awesome wiki!')
|
||||
end
|
||||
|
||||
it 'updates the commit message as the title is changed', :js do
|
||||
fill_in(:wiki_title, with: '& < > \ \ { } &')
|
||||
|
||||
expect(page).to have_field('wiki[message]', with: 'Update & < > \ \ { } &')
|
||||
end
|
||||
|
||||
it 'correctly escapes the commit message entities', :js do
|
||||
fill_in(:wiki_title, with: 'Wiki title')
|
||||
|
||||
expect(page).to have_field('wiki[message]', with: 'Update Wiki title')
|
||||
end
|
||||
|
||||
it 'shows a validation error message' do
|
||||
fill_in(:wiki_content, with: '')
|
||||
click_button('Save changes')
|
||||
|
||||
expect(page).to have_selector('.wiki-form')
|
||||
expect(page).to have_content('Edit Page')
|
||||
expect(page).to have_content('The form contains the following error:')
|
||||
expect(page).to have_content("Content can't be blank")
|
||||
expect(find('textarea#wiki_content').value).to eq('')
|
||||
end
|
||||
|
||||
it 'shows the emoji autocompletion dropdown', :js do
|
||||
find('#wiki_content').native.send_keys('')
|
||||
fill_in(:wiki_content, with: ':')
|
||||
|
||||
expect(page).to have_selector('.atwho-view')
|
||||
end
|
||||
|
||||
it 'shows the error message' do
|
||||
wiki_page.update(content: 'Update')
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(page).to have_content('Someone edited the page the same time you did.')
|
||||
end
|
||||
|
||||
it 'updates a page' do
|
||||
fill_in('Content', with: 'Updated Wiki Content')
|
||||
click_on('Save changes')
|
||||
|
||||
expect(page).to have_content('Updated Wiki Content')
|
||||
end
|
||||
|
||||
it 'cancels editing of a page' do
|
||||
page.within(:css, '.wiki-form .form-actions') do
|
||||
click_on('Cancel')
|
||||
end
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, wiki_page))
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki file attachments'
|
||||
end
|
||||
|
||||
context 'in a group namespace' do
|
||||
let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
|
||||
|
||||
it 'updates a page', :js do
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field('wiki[message]', with: 'Update home')
|
||||
|
||||
fill_in(:wiki_content, with: 'My awesome wiki!')
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(page).to have_content('Home')
|
||||
expect(page).to have_content("Last edited by #{user.name}")
|
||||
expect(page).to have_content('My awesome wiki!')
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki file attachments'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the page is in a subdir' do
|
||||
let!(:project) { create(:project, :wiki_repo) }
|
||||
let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) }
|
||||
let(:page_name) { 'page_name' }
|
||||
let(:page_dir) { "foo/bar/#{page_name}" }
|
||||
let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, title: page_dir, content: 'Home page') }
|
||||
|
||||
before do
|
||||
visit(project_wiki_edit_path(project, wiki_page))
|
||||
end
|
||||
|
||||
it 'moves the page to the root folder' do
|
||||
fill_in(:wiki_title, with: "/#{page_name}")
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, page_name))
|
||||
end
|
||||
|
||||
it 'moves the page to other dir' do
|
||||
new_page_dir = "foo1/bar1/#{page_name}"
|
||||
|
||||
fill_in(:wiki_title, with: new_page_dir)
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, new_page_dir))
|
||||
end
|
||||
|
||||
it 'remains in the same place if title has not changed' do
|
||||
original_path = project_wiki_path(project, wiki_page)
|
||||
|
||||
fill_in(:wiki_title, with: page_name)
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(original_path)
|
||||
end
|
||||
|
||||
it 'can be moved to a different dir with a different name' do
|
||||
new_page_dir = "foo1/bar1/new_page_name"
|
||||
|
||||
fill_in(:wiki_title, with: new_page_dir)
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, new_page_dir))
|
||||
end
|
||||
|
||||
it 'can be renamed and moved to the root folder' do
|
||||
new_name = 'new_page_name'
|
||||
|
||||
fill_in(:wiki_title, with: "/#{new_name}")
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, new_name))
|
||||
end
|
||||
|
||||
it 'squishes the title before creating the page' do
|
||||
new_page_dir = " foo1 / bar1 / #{page_name} "
|
||||
|
||||
fill_in(:wiki_title, with: new_page_dir)
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}"))
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki file attachments'
|
||||
end
|
||||
|
||||
context 'when an existing page exceeds the content size limit' do
|
||||
let_it_be(:project) { create(:project, :wiki_repo) }
|
||||
let!(:wiki_page) { create(:wiki_page, wiki: project.wiki, content: "one\ntwo\nthree") }
|
||||
|
||||
before do
|
||||
stub_application_setting(wiki_page_max_content_bytes: 10)
|
||||
|
||||
visit wiki_page_path(wiki_page.wiki, wiki_page, action: :edit)
|
||||
end
|
||||
|
||||
it 'allows changing the title if the content does not change' do
|
||||
fill_in 'Title', with: 'new title'
|
||||
click_on 'Save changes'
|
||||
|
||||
expect(page).to have_content('Wiki was successfully updated.')
|
||||
end
|
||||
|
||||
it 'shows a validation error when trying to change the content' do
|
||||
fill_in 'Content', with: 'new content'
|
||||
click_on 'Save changes'
|
||||
|
||||
expect(page).to have_content('The form contains the following error:')
|
||||
expect(page).to have_content('Content is too long (11 Bytes). The maximum size is 10 Bytes.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,108 +2,86 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User views empty wiki' do
|
||||
let(:user) { create(:user) }
|
||||
let(:confluence_link) { 'Enable the Confluence Wiki integration' }
|
||||
let(:element) { page.find('.row.empty-state') }
|
||||
RSpec.describe 'Project > User views empty wiki' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
shared_examples 'empty wiki and accessible issues' do
|
||||
it 'show "issue tracker" message' do
|
||||
visit(project_wikis_path(project))
|
||||
let(:wiki) { create(:project_wiki, project: project) }
|
||||
|
||||
expect(element).to have_content('This project has no wiki pages')
|
||||
expect(element).to have_content('You must be a project member')
|
||||
expect(element).to have_content('improve the wiki for this project')
|
||||
expect(element).to have_link("issue tracker", href: project_issues_path(project))
|
||||
expect(element).to have_link("Suggest wiki improvement", href: new_project_issue_path(project))
|
||||
expect(element).to have_no_link(confluence_link)
|
||||
end
|
||||
end
|
||||
it_behaves_like 'User views empty wiki' do
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
shared_examples 'empty wiki and non-accessible issues' do
|
||||
it 'does not show "issue tracker" message' do
|
||||
visit(project_wikis_path(project))
|
||||
it_behaves_like 'empty wiki message', issuable: true
|
||||
|
||||
expect(element).to have_content('This project has no wiki pages')
|
||||
expect(element).to have_content('You must be a project member')
|
||||
expect(element).to have_no_link('Suggest wiki improvement')
|
||||
expect(element).to have_no_link(confluence_link)
|
||||
end
|
||||
end
|
||||
context 'when issue tracker is private' do
|
||||
let(:project) { create(:project, :public, :issues_private) }
|
||||
|
||||
context 'when user is logged out and issue tracker is public' do
|
||||
let(:project) { create(:project, :public, :wiki_repo) }
|
||||
it_behaves_like 'empty wiki message', issuable: false
|
||||
end
|
||||
|
||||
it_behaves_like 'empty wiki and accessible issues'
|
||||
end
|
||||
context 'when issue tracker is disabled' do
|
||||
let(:project) { create(:project, :public, :issues_disabled) }
|
||||
|
||||
context 'when user is logged in and not a member' do
|
||||
let(:project) { create(:project, :public, :wiki_repo) }
|
||||
it_behaves_like 'empty wiki message', issuable: false
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
context 'and user is logged in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'and user is not a member' do
|
||||
it_behaves_like 'empty wiki message', issuable: true
|
||||
end
|
||||
|
||||
context 'and user is a member' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'empty wiki message', writable: true, issuable: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'empty wiki and accessible issues'
|
||||
end
|
||||
context 'when project is private' do
|
||||
let(:project) { create(:project, :private) }
|
||||
|
||||
context 'when issue tracker is private' do
|
||||
let(:project) { create(:project, :public, :wiki_repo, :issues_private) }
|
||||
it_behaves_like 'wiki is not found'
|
||||
|
||||
it_behaves_like 'empty wiki and non-accessible issues'
|
||||
end
|
||||
context 'and user is logged in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when issue tracker is disabled' do
|
||||
let(:project) { create(:project, :public, :wiki_repo, :issues_disabled) }
|
||||
context 'and user is not a member' do
|
||||
it_behaves_like 'wiki is not found'
|
||||
end
|
||||
|
||||
it_behaves_like 'empty wiki and non-accessible issues'
|
||||
end
|
||||
context 'and user is a member' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'when user is logged in and a member' do
|
||||
let(:project) { create(:project, :public) }
|
||||
it_behaves_like 'empty wiki message', writable: true, issuable: true
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project.add_developer(user)
|
||||
end
|
||||
context 'and user is a maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'shows "create first page" message' do
|
||||
visit(project_wikis_path(project))
|
||||
it_behaves_like 'empty wiki message', writable: true, issuable: true, confluence: true
|
||||
|
||||
expect(element).to have_content('your project', count: 2)
|
||||
context 'and Confluence is already enabled' do
|
||||
before do
|
||||
create(:confluence_service, project: project)
|
||||
end
|
||||
|
||||
element.click_link 'Create your first page'
|
||||
|
||||
expect(page).to have_button('Create page')
|
||||
end
|
||||
|
||||
it 'does not show the "enable confluence" button' do
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
expect(element).to have_no_link(confluence_link)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is logged in and an admin' do
|
||||
let(:project) { create(:project, :public, :wiki_repo) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'shows the "enable confluence" button' do
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
expect(element).to have_link(confluence_link)
|
||||
end
|
||||
|
||||
it 'does not show "enable confluence" button if confluence is already enabled' do
|
||||
create(:confluence_service, project: project)
|
||||
|
||||
visit(project_wikis_path(project))
|
||||
|
||||
expect(element).to have_no_link(confluence_link)
|
||||
it_behaves_like 'empty wiki message', writable: true, issuable: true, confluence: false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe 'Project wikis' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:wiki) { create(:project_wiki, user: user, project: project) }
|
||||
let(:project) { create(:project, namespace: user.namespace, creator: user) }
|
||||
|
||||
it_behaves_like 'User creates wiki page'
|
||||
it_behaves_like 'User deletes wiki page'
|
||||
it_behaves_like 'User previews wiki changes'
|
||||
it_behaves_like 'User updates wiki page'
|
||||
it_behaves_like 'User uses wiki shortcuts'
|
||||
it_behaves_like 'User views AsciiDoc page with includes'
|
||||
it_behaves_like 'User views a wiki page'
|
||||
it_behaves_like 'User views wiki pages'
|
||||
it_behaves_like 'User views wiki sidebar'
|
||||
end
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Releases (JavaScript fixtures)' do
|
||||
include ApiHelpers
|
||||
include JavaScriptFixturesHelpers
|
||||
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
|
|
@ -68,16 +69,14 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
|
|||
link_type: :runbook)
|
||||
end
|
||||
|
||||
before(:all) do
|
||||
clean_frontend_fixtures('api/releases/')
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
remove_repository(project)
|
||||
end
|
||||
|
||||
describe API::Releases, '(JavaScript fixtures)', type: :request do
|
||||
include ApiHelpers
|
||||
describe API::Releases, type: :request do
|
||||
before(:all) do
|
||||
clean_frontend_fixtures('api/releases/')
|
||||
end
|
||||
|
||||
it 'api/releases/release.json' do
|
||||
get api("/projects/#{project.id}/releases/#{release.tag}", admin)
|
||||
|
|
@ -85,4 +84,22 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
|
|||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
graphql_query_path = 'releases/queries/all_releases.query.graphql'
|
||||
|
||||
describe "~/#{graphql_query_path}", type: :request do
|
||||
include GraphqlHelpers
|
||||
|
||||
before(:all) do
|
||||
clean_frontend_fixtures('graphql/releases/')
|
||||
end
|
||||
|
||||
it "graphql/#{graphql_query_path}.json" do
|
||||
query = File.read(File.join(Rails.root, '/app/assets/javascripts', graphql_query_path))
|
||||
|
||||
post_graphql(query, current_user: admin, variables: { fullPath: project.full_path })
|
||||
|
||||
expect_graphql_errors_to_be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
import { mount, createWrapper } from '@vue/test-utils';
|
||||
import { getByText as getByTextHelper } from '@testing-library/dom';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import MemberSource from '~/vue_shared/components/members/table/member_source.vue';
|
||||
|
||||
describe('MemberSource', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = propsData => {
|
||||
wrapper = mount(MemberSource, {
|
||||
propsData: {
|
||||
memberSource: {
|
||||
id: 102,
|
||||
name: 'Foo bar',
|
||||
webUrl: 'https://gitlab.com/groups/foo-bar',
|
||||
},
|
||||
...propsData,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getByText = (text, options) =>
|
||||
createWrapper(getByTextHelper(wrapper.element, text, options));
|
||||
|
||||
const getTooltipDirective = elementWrapper => getBinding(elementWrapper.element, 'gl-tooltip');
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('direct member', () => {
|
||||
it('displays "Direct member"', () => {
|
||||
createComponent({
|
||||
isDirectMember: true,
|
||||
});
|
||||
|
||||
expect(getByText('Direct member').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inherited member', () => {
|
||||
let sourceGroupLink;
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
isDirectMember: false,
|
||||
});
|
||||
|
||||
sourceGroupLink = getByText('Foo bar');
|
||||
});
|
||||
|
||||
it('displays a link to source group', () => {
|
||||
createComponent({
|
||||
isDirectMember: false,
|
||||
});
|
||||
|
||||
expect(sourceGroupLink.exists()).toBe(true);
|
||||
expect(sourceGroupLink.attributes('href')).toBe('https://gitlab.com/groups/foo-bar');
|
||||
});
|
||||
|
||||
it('displays tooltip with "Inherited"', () => {
|
||||
const tooltipDirective = getTooltipDirective(sourceGroupLink);
|
||||
|
||||
expect(tooltipDirective).not.toBeUndefined();
|
||||
expect(sourceGroupLink.attributes('title')).toBe('Inherited');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import { MEMBER_TYPES } from '~/vue_shared/components/members/constants';
|
||||
import { member as memberMock, group, invite, accessRequest } from '../mock_data';
|
||||
import MembersTableCell from '~/vue_shared/components/members/table/members_table_cell.vue';
|
||||
|
|
@ -10,6 +11,10 @@ describe('MemberList', () => {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isDirectMember: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('div', this.memberType);
|
||||
|
|
@ -17,20 +22,34 @@ describe('MemberList', () => {
|
|||
};
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
localVue.component('wrapped-component', WrappedComponent);
|
||||
|
||||
let wrapper;
|
||||
|
||||
const createComponent = propsData => {
|
||||
wrapper = mount(MembersTableCell, {
|
||||
localVue,
|
||||
propsData,
|
||||
scopedSlots: {
|
||||
default: '<wrapped-component :member-type="props.memberType" />',
|
||||
const createStore = (state = {}) => {
|
||||
return new Vuex.Store({
|
||||
state: {
|
||||
sourceId: 1,
|
||||
...state,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (propsData, state = {}) => {
|
||||
wrapper = mount(MembersTableCell, {
|
||||
localVue,
|
||||
propsData,
|
||||
store: createStore(state),
|
||||
scopedSlots: {
|
||||
default:
|
||||
'<wrapped-component :member-type="props.memberType" :is-direct-member="props.isDirectMember" />',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findWrappedComponent = () => wrapper.find(WrappedComponent);
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
|
|
@ -47,7 +66,31 @@ describe('MemberList', () => {
|
|||
({ member, expectedMemberType }) => {
|
||||
createComponent({ member });
|
||||
|
||||
expect(wrapper.find(WrappedComponent).props('memberType')).toBe(expectedMemberType);
|
||||
expect(findWrappedComponent().props('memberType')).toBe(expectedMemberType);
|
||||
},
|
||||
);
|
||||
|
||||
describe('isDirectMember', () => {
|
||||
it('returns `true` when member source has same ID as `sourceId`', () => {
|
||||
createComponent({
|
||||
member: {
|
||||
...memberMock,
|
||||
source: {
|
||||
...memberMock.source,
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(findWrappedComponent().props('isDirectMember')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns `false` when member is inherited', () => {
|
||||
createComponent({
|
||||
member: memberMock,
|
||||
});
|
||||
|
||||
expect(findWrappedComponent().props('isDirectMember')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import {
|
|||
getByTestId as getByTestIdHelper,
|
||||
} from '@testing-library/dom';
|
||||
import MembersTable from '~/vue_shared/components/members/table/members_table.vue';
|
||||
import MemberAvatar from '~/vue_shared/components/members/table/member_avatar.vue';
|
||||
import MemberSource from '~/vue_shared/components/members/table/member_source.vue';
|
||||
import * as initUserPopovers from '~/user_popovers';
|
||||
import { member as memberMock, invite, accessRequest } from '../mock_data';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
|
@ -44,20 +47,31 @@ describe('MemberList', () => {
|
|||
|
||||
describe('fields', () => {
|
||||
it.each`
|
||||
field | label
|
||||
${'source'} | ${'Source'}
|
||||
${'granted'} | ${'Access granted'}
|
||||
${'invited'} | ${'Invited'}
|
||||
${'requested'} | ${'Requested'}
|
||||
${'expires'} | ${'Access expires'}
|
||||
${'maxRole'} | ${'Max role'}
|
||||
${'expiration'} | ${'Expiration'}
|
||||
`('renders the $label field', ({ field, label }) => {
|
||||
field | label | member | expectedComponent
|
||||
${'account'} | ${'Account'} | ${memberMock} | ${MemberAvatar}
|
||||
${'source'} | ${'Source'} | ${memberMock} | ${MemberSource}
|
||||
${'granted'} | ${'Access granted'} | ${memberMock} | ${null}
|
||||
${'invited'} | ${'Invited'} | ${invite} | ${null}
|
||||
${'requested'} | ${'Requested'} | ${accessRequest} | ${null}
|
||||
${'expires'} | ${'Access expires'} | ${memberMock} | ${null}
|
||||
${'maxRole'} | ${'Max role'} | ${memberMock} | ${null}
|
||||
${'expiration'} | ${'Expiration'} | ${memberMock} | ${null}
|
||||
`('renders the $label field', ({ field, label, member, expectedComponent }) => {
|
||||
createComponent({
|
||||
members: [member],
|
||||
tableFields: [field],
|
||||
});
|
||||
|
||||
expect(getByText(label, { selector: '[role="columnheader"]' }).exists()).toBe(true);
|
||||
|
||||
if (expectedComponent) {
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-label="${label}"][role="cell"]`)
|
||||
.find(expectedComponent)
|
||||
.exists(),
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('renders "Actions" field for screen readers', () => {
|
||||
|
|
|
|||
|
|
@ -306,6 +306,38 @@ RSpec.describe IssuablesHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#reviewer_sidebar_data' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
subject { helper.reviewer_sidebar_data(user, merge_request: merge_request) }
|
||||
|
||||
context 'without merge_request' do
|
||||
let(:merge_request) { nil }
|
||||
|
||||
it 'returns hash of reviewer data' do
|
||||
is_expected.to eql({
|
||||
avatar_url: user.avatar_url,
|
||||
name: user.name,
|
||||
username: user.username
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with merge_request' do
|
||||
let(:merge_request) { build(:merge_request) }
|
||||
|
||||
where(can_merge: [true, false])
|
||||
|
||||
with_them do
|
||||
before do
|
||||
allow(merge_request).to receive(:can_be_merged_by?).and_return(can_merge)
|
||||
end
|
||||
|
||||
it { is_expected.to include({ can_merge: can_merge })}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#issuable_squash_option?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
|
|
|
|||
|
|
@ -41,11 +41,11 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
|
|||
context 'for web IDE edit actions' do
|
||||
it_behaves_like 'tracks and counts action' do
|
||||
def track_action(params)
|
||||
described_class.track_web_ide_edit_action(params)
|
||||
described_class.track_web_ide_edit_action(**params)
|
||||
end
|
||||
|
||||
def count_unique(params)
|
||||
described_class.count_web_ide_edit_actions(params)
|
||||
described_class.count_web_ide_edit_actions(**params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -53,11 +53,11 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
|
|||
context 'for SFE edit actions' do
|
||||
it_behaves_like 'tracks and counts action' do
|
||||
def track_action(params)
|
||||
described_class.track_sfe_edit_action(params)
|
||||
described_class.track_sfe_edit_action(**params)
|
||||
end
|
||||
|
||||
def count_unique(params)
|
||||
described_class.count_sfe_edit_actions(params)
|
||||
described_class.count_sfe_edit_actions(**params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -65,11 +65,11 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
|
|||
context 'for snippet editor edit actions' do
|
||||
it_behaves_like 'tracks and counts action' do
|
||||
def track_action(params)
|
||||
described_class.track_snippet_editor_edit_action(params)
|
||||
described_class.track_snippet_editor_edit_action(**params)
|
||||
end
|
||||
|
||||
def count_unique(params)
|
||||
described_class.count_snippet_editor_edit_actions(params)
|
||||
described_class.count_snippet_editor_edit_actions(**params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ RSpec.describe Gitlab::UsageDataCounters::TrackUniqueEvents, :clean_gitlab_redis
|
|||
let(:time) { Time.zone.now }
|
||||
|
||||
def track_event(params)
|
||||
track_unique_events.track_event(params)
|
||||
track_unique_events.track_event(**params)
|
||||
end
|
||||
|
||||
def count_unique(params)
|
||||
track_unique_events.count_unique_events(params)
|
||||
track_unique_events.count_unique_events(**params)
|
||||
end
|
||||
|
||||
context 'tracking an event' do
|
||||
|
|
|
|||
|
|
@ -13,16 +13,16 @@ module WikiHelpers
|
|||
find('.svg-content .js-lazy-loaded') if example.nil? || example.metadata.key?(:js)
|
||||
end
|
||||
|
||||
def upload_file_to_wiki(container, user, file_name)
|
||||
opts = {
|
||||
def upload_file_to_wiki(wiki, user, file_name)
|
||||
params = {
|
||||
file_name: file_name,
|
||||
file_content: File.read(expand_fixture_path(file_name))
|
||||
}
|
||||
|
||||
::Wikis::CreateAttachmentService.new(
|
||||
container: container,
|
||||
container: wiki.container,
|
||||
current_user: user,
|
||||
params: opts
|
||||
).execute[:result][:file_path]
|
||||
params: params
|
||||
).execute.dig(:result, :file_path)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ RSpec.shared_examples 'wiki controller actions' do
|
|||
where(:file_name) { ['dk.png', 'unsanitized.svg', 'git-cheat-sheet.pdf'] }
|
||||
|
||||
with_them do
|
||||
let(:id) { upload_file_to_wiki(container, user, file_name) }
|
||||
let(:id) { upload_file_to_wiki(wiki, user, file_name) }
|
||||
|
||||
it 'delivers the file with the correct headers' do
|
||||
subject
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Requires a context containing:
|
||||
# project
|
||||
# wiki
|
||||
|
||||
RSpec.shared_examples 'wiki file attachments' do
|
||||
include DropzoneHelper
|
||||
|
||||
context 'uploading attachments', :js do
|
||||
let(:wiki) { project.wiki }
|
||||
|
||||
def attach_with_dropzone(wait = false)
|
||||
dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, wait)
|
||||
end
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Requires a context containing:
|
||||
# wiki
|
||||
# user
|
||||
|
||||
RSpec.shared_examples 'User creates wiki page' do
|
||||
include WikiHelpers
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context "when wiki is empty" do
|
||||
before do |example|
|
||||
visit wiki_path(wiki)
|
||||
|
||||
wait_for_svg_to_be_loaded(example)
|
||||
|
||||
click_link "Create your first page"
|
||||
end
|
||||
|
||||
it "shows validation error message" do
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_content, with: "")
|
||||
|
||||
click_on("Create page")
|
||||
end
|
||||
|
||||
expect(page).to have_content("The form contains the following error:").and have_content("Content can't be blank")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_content, with: "[link test](test)")
|
||||
|
||||
click_on("Create page")
|
||||
end
|
||||
|
||||
expect(page).to have_content("Home").and have_content("link test")
|
||||
|
||||
click_link("link test")
|
||||
|
||||
expect(page).to have_content("Create New Page")
|
||||
end
|
||||
|
||||
it "shows non-escaped link in the pages list" do
|
||||
fill_in(:wiki_title, with: "one/two/three-test")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_content, with: "wiki content")
|
||||
|
||||
click_on("Create page")
|
||||
end
|
||||
|
||||
expect(current_path).to include("one/two/three-test")
|
||||
expect(page).to have_link(href: wiki_page_path(wiki, 'one/two/three-test'))
|
||||
end
|
||||
|
||||
it "has `Create home` as a commit message", :js do
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_field("wiki[message]", with: "Create home")
|
||||
end
|
||||
|
||||
it "creates a page from the home page" do
|
||||
fill_in(:wiki_content, with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n# Wiki header\n")
|
||||
fill_in(:wiki_message, with: "Adding links to wiki")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
click_button("Create page")
|
||||
end
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, "home"))
|
||||
expect(page).to have_content("test GitLab API doc Rake tasks Wiki header")
|
||||
.and have_content("Home")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_header_with_correct_id_and_link(1, "Wiki header", "wiki-header")
|
||||
|
||||
click_link("test")
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, "test"))
|
||||
|
||||
page.within(:css, ".nav-text") do
|
||||
expect(page).to have_content("Create New Page")
|
||||
end
|
||||
|
||||
click_link("Home")
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, "home"))
|
||||
|
||||
click_link("GitLab API")
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, "api"))
|
||||
|
||||
page.within(:css, ".nav-text") do
|
||||
expect(page).to have_content("Create")
|
||||
end
|
||||
|
||||
click_link("Home")
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, "home"))
|
||||
|
||||
click_link("Rake tasks")
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, "raketasks"))
|
||||
|
||||
page.within(:css, ".nav-text") do
|
||||
expect(page).to have_content("Create")
|
||||
end
|
||||
end
|
||||
|
||||
it "creates ASCII wiki with LaTeX blocks", :js do
|
||||
stub_application_setting(plantuml_url: "http://localhost", plantuml_enabled: true)
|
||||
|
||||
ascii_content = <<~MD
|
||||
:stem: latexmath
|
||||
|
||||
[stem]
|
||||
++++
|
||||
\\sqrt{4} = 2
|
||||
++++
|
||||
|
||||
another part
|
||||
|
||||
[latexmath]
|
||||
++++
|
||||
\\beta_x \\gamma
|
||||
++++
|
||||
|
||||
stem:[2+2] is 4
|
||||
MD
|
||||
|
||||
find("#wiki_format option[value=asciidoc]").select_option
|
||||
|
||||
fill_in(:wiki_content, with: ascii_content)
|
||||
|
||||
page.within(".wiki-form") do
|
||||
click_button("Create page")
|
||||
end
|
||||
|
||||
page.within ".md" do
|
||||
expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4")
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates a wiki page with Org markup', :aggregate_failures do
|
||||
org_content = <<~ORG
|
||||
* Heading
|
||||
** Subheading
|
||||
[[home][Link to Home]]
|
||||
ORG
|
||||
|
||||
page.within('.wiki-form') do
|
||||
find('#wiki_format option[value=org]').select_option
|
||||
fill_in(:wiki_content, with: org_content)
|
||||
click_button('Create page')
|
||||
end
|
||||
|
||||
expect(page).to have_selector('h1', text: 'Heading')
|
||||
expect(page).to have_selector('h2', text: 'Subheading')
|
||||
expect(page).to have_link(href: wiki_page_path(wiki, 'home'))
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki file attachments'
|
||||
end
|
||||
|
||||
context "when wiki is not empty", :js do
|
||||
before do
|
||||
create(:wiki_page, wiki: wiki, title: 'home', content: 'Home page')
|
||||
|
||||
visit wiki_path(wiki)
|
||||
end
|
||||
|
||||
context "via the `new wiki page` page" do
|
||||
it "creates a page with a single word" do
|
||||
click_link("New page")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_title, with: "foo")
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
end
|
||||
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field("wiki[message]", with: "Create foo")
|
||||
|
||||
click_button("Create page")
|
||||
|
||||
expect(page).to have_content("foo")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_content("My awesome wiki!")
|
||||
end
|
||||
|
||||
it "creates a page with spaces in the name" do
|
||||
click_link("New page")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_title, with: "Spaces in the name")
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
end
|
||||
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field("wiki[message]", with: "Create Spaces in the name")
|
||||
|
||||
click_button("Create page")
|
||||
|
||||
expect(page).to have_content("Spaces in the name")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_content("My awesome wiki!")
|
||||
end
|
||||
|
||||
it "creates a page with hyphens in the name" do
|
||||
click_link("New page")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_title, with: "hyphens-in-the-name")
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
end
|
||||
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field("wiki[message]", with: "Create hyphens in the name")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
fill_in(:wiki_content, with: "My awesome wiki!")
|
||||
|
||||
click_button("Create page")
|
||||
end
|
||||
|
||||
expect(page).to have_content("hyphens in the name")
|
||||
.and have_content("Last edited by #{user.name}")
|
||||
.and have_content("My awesome wiki!")
|
||||
end
|
||||
end
|
||||
|
||||
it "shows the emoji autocompletion dropdown" do
|
||||
click_link("New page")
|
||||
|
||||
page.within(".wiki-form") do
|
||||
find("#wiki_content").native.send_keys("")
|
||||
|
||||
fill_in(:wiki_content, with: ":")
|
||||
end
|
||||
|
||||
expect(page).to have_selector(".atwho-view")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Requires a context containing:
|
||||
# wiki
|
||||
# user
|
||||
|
||||
RSpec.shared_examples 'User deletes wiki page' do
|
||||
include WikiHelpers
|
||||
|
||||
let(:wiki_page) { create(:wiki_page, wiki: wiki) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
visit wiki_page_path(wiki, wiki_page)
|
||||
end
|
||||
|
||||
it 'deletes a page', :js do
|
||||
click_on('Edit')
|
||||
click_on('Delete')
|
||||
find('.modal-footer .btn-danger').click
|
||||
|
||||
expect(page).to have_content('Page was successfully deleted')
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Requires a context containing:
|
||||
# wiki
|
||||
# user
|
||||
|
||||
RSpec.shared_examples 'User previews wiki changes' do
|
||||
let(:wiki_page) { build(:wiki_page, wiki: wiki) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
shared_examples 'relative links' do
|
||||
let_it_be(:page_content) do
|
||||
<<~HEREDOC
|
||||
Some text so key event for [ does not trigger an incorrect replacement.
|
||||
[regular link](regular)
|
||||
[relative link 1](../relative)
|
||||
[relative link 2](./relative)
|
||||
[relative link 3](./e/f/relative)
|
||||
[spaced link](title with spaces)
|
||||
HEREDOC
|
||||
end
|
||||
|
||||
def relative_path(path)
|
||||
(Pathname.new(wiki.wiki_base_path) + File.dirname(wiki_page.path).tr(' ', '-') + path).to_s
|
||||
end
|
||||
|
||||
shared_examples "rewrites relative links" do
|
||||
specify do
|
||||
expect(element).to have_link('regular link', href: wiki.wiki_base_path + '/regular')
|
||||
expect(element).to have_link('spaced link', href: wiki.wiki_base_path + '/title%20with%20spaces')
|
||||
|
||||
expect(element).to have_link('relative link 1', href: relative_path('../relative'))
|
||||
expect(element).to have_link('relative link 2', href: relative_path('./relative'))
|
||||
expect(element).to have_link('relative link 3', href: relative_path('./e/f/relative'))
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are no spaces or hyphens in the page name" do
|
||||
let(:wiki_page) { build(:wiki_page, wiki: wiki, title: 'a/b/c/d', content: page_content) }
|
||||
|
||||
it_behaves_like 'rewrites relative links'
|
||||
end
|
||||
|
||||
context "when there are spaces in the page name" do
|
||||
let(:wiki_page) { build(:wiki_page, wiki: wiki, title: 'a page/b page/c page/d page', content: page_content) }
|
||||
|
||||
it_behaves_like 'rewrites relative links'
|
||||
end
|
||||
|
||||
context "when there are hyphens in the page name" do
|
||||
let(:wiki_page) { build(:wiki_page, wiki: wiki, title: 'a-page/b-page/c-page/d-page', content: page_content) }
|
||||
|
||||
it_behaves_like 'rewrites relative links'
|
||||
end
|
||||
end
|
||||
|
||||
context "when rendering a new wiki page", :js do
|
||||
before do
|
||||
wiki_page.create # rubocop:disable Rails/SaveBang
|
||||
visit wiki_page_path(wiki, wiki_page)
|
||||
end
|
||||
|
||||
it_behaves_like 'relative links' do
|
||||
let(:element) { page.find('.js-wiki-page-content') }
|
||||
end
|
||||
end
|
||||
|
||||
context "when previewing an existing wiki page", :js do
|
||||
let(:preview) { page.find('.md-preview-holder') }
|
||||
|
||||
before do
|
||||
wiki_page.create # rubocop:disable Rails/SaveBang
|
||||
visit wiki_page_path(wiki, wiki_page, action: :edit)
|
||||
end
|
||||
|
||||
it_behaves_like 'relative links' do
|
||||
before do
|
||||
click_on 'Preview'
|
||||
end
|
||||
|
||||
let(:element) { preview }
|
||||
end
|
||||
|
||||
it 'renders content with CommonMark' do
|
||||
fill_in :wiki_content, with: "1. one\n - sublist\n"
|
||||
click_on "Preview"
|
||||
|
||||
# the above generates two separate lists (not embedded) in CommonMark
|
||||
expect(preview).to have_content("sublist")
|
||||
expect(preview).not_to have_xpath("//ol//li//ul")
|
||||
end
|
||||
|
||||
it "does not linkify double brackets inside code blocks as expected" do
|
||||
fill_in :wiki_content, with: <<-HEREDOC
|
||||
`[[do_not_linkify]]`
|
||||
```
|
||||
[[also_do_not_linkify]]
|
||||
```
|
||||
HEREDOC
|
||||
click_on "Preview"
|
||||
|
||||
expect(preview).to have_content("do_not_linkify")
|
||||
expect(preview).to have_content('[[do_not_linkify]]')
|
||||
expect(preview).to have_content('[[also_do_not_linkify]]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Requires a context containing:
|
||||
# wiki
|
||||
# user
|
||||
|
||||
RSpec.shared_examples 'User updates wiki page' do
|
||||
include WikiHelpers
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when wiki is empty' do
|
||||
before do |example|
|
||||
visit(wiki_path(wiki))
|
||||
|
||||
wait_for_svg_to_be_loaded(example)
|
||||
|
||||
click_link "Create your first page"
|
||||
end
|
||||
|
||||
it 'redirects back to the home edit page' do
|
||||
page.within(:css, '.wiki-form .form-actions') do
|
||||
click_on('Cancel')
|
||||
end
|
||||
|
||||
expect(current_path).to eq wiki_path(wiki)
|
||||
end
|
||||
|
||||
it 'updates a page that has a path', :js do
|
||||
fill_in(:wiki_title, with: 'one/two/three-test')
|
||||
|
||||
page.within '.wiki-form' do
|
||||
fill_in(:wiki_content, with: 'wiki content')
|
||||
click_on('Create page')
|
||||
end
|
||||
|
||||
expect(current_path).to include('one/two/three-test')
|
||||
expect(find('.wiki-pages')).to have_content('three')
|
||||
|
||||
first(:link, text: 'three').click
|
||||
|
||||
expect(find('.nav-text')).to have_content('three')
|
||||
|
||||
click_on('Edit')
|
||||
|
||||
expect(current_path).to include('one/two/three-test')
|
||||
expect(page).to have_content('Edit Page')
|
||||
|
||||
fill_in('Content', with: 'Updated Wiki Content')
|
||||
click_on('Save changes')
|
||||
|
||||
expect(page).to have_content('Updated Wiki Content')
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki file attachments'
|
||||
end
|
||||
|
||||
context 'when wiki is not empty' do
|
||||
let!(:wiki_page) { create(:wiki_page, wiki: wiki, title: 'home', content: 'Home page') }
|
||||
|
||||
before do
|
||||
visit(wiki_path(wiki))
|
||||
|
||||
click_link('Edit')
|
||||
end
|
||||
|
||||
it 'updates a page', :js do
|
||||
# Commit message field should have correct value.
|
||||
expect(page).to have_field('wiki[message]', with: 'Update home')
|
||||
|
||||
fill_in(:wiki_content, with: 'My awesome wiki!')
|
||||
click_button('Save changes')
|
||||
|
||||
expect(page).to have_content('Home')
|
||||
expect(page).to have_content("Last edited by #{user.name}")
|
||||
expect(page).to have_content('My awesome wiki!')
|
||||
end
|
||||
|
||||
it 'updates the commit message as the title is changed', :js do
|
||||
fill_in(:wiki_title, with: '& < > \ \ { } &')
|
||||
|
||||
expect(page).to have_field('wiki[message]', with: 'Update & < > \ \ { } &')
|
||||
end
|
||||
|
||||
it 'correctly escapes the commit message entities', :js do
|
||||
fill_in(:wiki_title, with: 'Wiki title')
|
||||
|
||||
expect(page).to have_field('wiki[message]', with: 'Update Wiki title')
|
||||
end
|
||||
|
||||
it 'shows a validation error message' do
|
||||
fill_in(:wiki_content, with: '')
|
||||
click_button('Save changes')
|
||||
|
||||
expect(page).to have_selector('.wiki-form')
|
||||
expect(page).to have_content('Edit Page')
|
||||
expect(page).to have_content('The form contains the following error:')
|
||||
expect(page).to have_content("Content can't be blank")
|
||||
expect(find('textarea#wiki_content').value).to eq('')
|
||||
end
|
||||
|
||||
it 'shows the emoji autocompletion dropdown', :js do
|
||||
find('#wiki_content').native.send_keys('')
|
||||
fill_in(:wiki_content, with: ':')
|
||||
|
||||
expect(page).to have_selector('.atwho-view')
|
||||
end
|
||||
|
||||
it 'shows the error message' do
|
||||
wiki_page.update(content: 'Update') # rubocop:disable Rails/SaveBang
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(page).to have_content('Someone edited the page the same time you did.')
|
||||
end
|
||||
|
||||
it 'updates a page' do
|
||||
fill_in('Content', with: 'Updated Wiki Content')
|
||||
click_on('Save changes')
|
||||
|
||||
expect(page).to have_content('Updated Wiki Content')
|
||||
end
|
||||
|
||||
it 'cancels editing of a page' do
|
||||
page.within(:css, '.wiki-form .form-actions') do
|
||||
click_on('Cancel')
|
||||
end
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, wiki_page))
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki file attachments'
|
||||
end
|
||||
|
||||
context 'when the page is in a subdir' do
|
||||
let(:page_name) { 'page_name' }
|
||||
let(:page_dir) { "foo/bar/#{page_name}" }
|
||||
let!(:wiki_page) { create(:wiki_page, wiki: wiki, title: page_dir, content: 'Home page') }
|
||||
|
||||
before do
|
||||
visit wiki_page_path(wiki, wiki_page, action: :edit)
|
||||
end
|
||||
|
||||
it 'moves the page to the root folder' do
|
||||
fill_in(:wiki_title, with: "/#{page_name}")
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, page_name))
|
||||
end
|
||||
|
||||
it 'moves the page to other dir' do
|
||||
new_page_dir = "foo1/bar1/#{page_name}"
|
||||
|
||||
fill_in(:wiki_title, with: new_page_dir)
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, new_page_dir))
|
||||
end
|
||||
|
||||
it 'remains in the same place if title has not changed' do
|
||||
original_path = wiki_page_path(wiki, wiki_page)
|
||||
|
||||
fill_in(:wiki_title, with: page_name)
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(original_path)
|
||||
end
|
||||
|
||||
it 'can be moved to a different dir with a different name' do
|
||||
new_page_dir = "foo1/bar1/new_page_name"
|
||||
|
||||
fill_in(:wiki_title, with: new_page_dir)
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, new_page_dir))
|
||||
end
|
||||
|
||||
it 'can be renamed and moved to the root folder' do
|
||||
new_name = 'new_page_name'
|
||||
|
||||
fill_in(:wiki_title, with: "/#{new_name}")
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, new_name))
|
||||
end
|
||||
|
||||
it 'squishes the title before creating the page' do
|
||||
new_page_dir = " foo1 / bar1 / #{page_name} "
|
||||
|
||||
fill_in(:wiki_title, with: new_page_dir)
|
||||
|
||||
click_button('Save changes')
|
||||
|
||||
expect(current_path).to eq(wiki_page_path(wiki, "foo1/bar1/#{page_name}"))
|
||||
end
|
||||
|
||||
it_behaves_like 'wiki file attachments'
|
||||
end
|
||||
|
||||
context 'when an existing page exceeds the content size limit' do
|
||||
let!(:wiki_page) { create(:wiki_page, wiki: wiki, content: "one\ntwo\nthree") }
|
||||
|
||||
before do
|
||||
stub_application_setting(wiki_page_max_content_bytes: 10)
|
||||
|
||||
visit wiki_page_path(wiki_page.wiki, wiki_page, action: :edit)
|
||||
end
|
||||
|
||||
it 'allows changing the title if the content does not change' do
|
||||
fill_in 'Title', with: 'new title'
|
||||
click_on 'Save changes'
|
||||
|
||||
expect(page).to have_content('Wiki was successfully updated.')
|
||||
end
|
||||
|
||||
it 'shows a validation error when trying to change the content' do
|
||||
fill_in 'Content', with: 'new content'
|
||||
click_on 'Save changes'
|
||||
|
||||
expect(page).to have_content('The form contains the following error:')
|
||||
expect(page).to have_content('Content is too long (11 Bytes). The maximum size is 10 Bytes.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Requires a context containing:
|
||||
# wiki
|
||||
# user
|
||||
|
||||
RSpec.shared_examples 'User uses wiki shortcuts' do
|
||||
let(:wiki_page) { create(:wiki_page, wiki: wiki, title: 'home', content: 'Home page') }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
visit wiki_page_path(wiki, wiki_page)
|
||||
end
|
||||
|
||||
it 'Visit edit wiki page using "e" keyboard shortcut', :js do
|
||||
find('body').native.send_key('e')
|
||||
|
||||
expect(find('.wiki-page-title')).to have_content('Edit Page')
|
||||
end
|
||||
end
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User views AsciiDoc page with includes', :js do
|
||||
let_it_be(:user) { create(:user) }
|
||||
RSpec.shared_examples 'User views AsciiDoc page with includes' do
|
||||
let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' }
|
||||
let(:project) { create(:project, :public, :wiki_repo) }
|
||||
let!(:included_wiki_page) { create_wiki_page('included_page', content: 'Content from the included page')}
|
||||
let!(:wiki_page) { create_wiki_page('home', content: "Content from the main page.\ninclude::included_page.asciidoc[]") }
|
||||
|
||||
|
|
@ -16,16 +12,16 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
|
|||
format: :asciidoc
|
||||
}
|
||||
|
||||
create(:wiki_page, wiki: project.wiki, **attrs)
|
||||
create(:wiki_page, wiki: wiki, **attrs)
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when the file being included exists' do
|
||||
context 'when the file being included exists', :js do
|
||||
it 'includes the file contents' do
|
||||
visit(project_wiki_path(project, wiki_page))
|
||||
visit(wiki_page_path(wiki, wiki_page))
|
||||
|
||||
page.within(:css, wiki_content_selector) do
|
||||
expect(page).to have_content('Content from the main page. Content from the included page')
|
||||
|
|
@ -34,8 +30,10 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
|
|||
|
||||
context 'when there are multiple versions of the wiki pages' do
|
||||
before do
|
||||
# rubocop:disable Rails/SaveBang
|
||||
included_wiki_page.update(message: 'updated included file', content: 'Updated content from the included page')
|
||||
wiki_page.update(message: 'updated wiki page', content: "Updated content from the main page.\ninclude::included_page.asciidoc[]")
|
||||
# rubocop:enable Rails/SaveBang
|
||||
end
|
||||
|
||||
let(:latest_version_id) { wiki_page.versions.first.id }
|
||||
|
|
@ -43,7 +41,7 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
|
|||
|
||||
context 'viewing the latest version' do
|
||||
it 'includes the latest content' do
|
||||
visit(project_wiki_path(project, wiki_page, version_id: latest_version_id))
|
||||
visit(wiki_page_path(wiki, wiki_page, version_id: latest_version_id))
|
||||
|
||||
page.within(:css, wiki_content_selector) do
|
||||
expect(page).to have_content('Updated content from the main page. Updated content from the included page')
|
||||
|
|
@ -53,7 +51,7 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
|
|||
|
||||
context 'viewing the original version' do
|
||||
it 'includes the content from the original version' do
|
||||
visit(project_wiki_path(project, wiki_page, version_id: oldest_version_id))
|
||||
visit(wiki_page_path(wiki, wiki_page, version_id: oldest_version_id))
|
||||
|
||||
page.within(:css, wiki_content_selector) do
|
||||
expect(page).to have_content('Content from the main page. Content from the included page')
|
||||
|
|
@ -63,13 +61,13 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the file being included does not exist' do
|
||||
context 'when the file being included does not exist', :js do
|
||||
before do
|
||||
included_wiki_page.delete
|
||||
end
|
||||
|
||||
it 'outputs an error' do
|
||||
visit(project_wiki_path(project, wiki_page))
|
||||
visit(wiki_page_path(wiki, wiki_page))
|
||||
|
||||
page.within(:css, wiki_content_selector) do
|
||||
expect(page).to have_content('Content from the main page. [ERROR: include::included_page.asciidoc[] - unresolved directive]')
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Requires a context containing:
|
||||
# wiki
|
||||
|
||||
RSpec.shared_examples 'User views empty wiki' do
|
||||
let(:element) { page.find('.row.empty-state') }
|
||||
let(:container_name) { wiki.container.class.name.humanize(capitalize: false) }
|
||||
let(:confluence_link) { 'Enable the Confluence Wiki integration' }
|
||||
|
||||
shared_examples 'wiki is not found' do
|
||||
it 'shows an error message' do
|
||||
visit wiki_path(wiki)
|
||||
|
||||
if @current_user
|
||||
expect(page).to have_content('Page Not Found')
|
||||
else
|
||||
expect(page).to have_content('You need to sign in')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'empty wiki message' do |writable: false, issuable: false, confluence: false|
|
||||
# This mirrors the logic in:
|
||||
# - app/views/shared/empty_states/_wikis.html.haml
|
||||
# - WikiHelper#wiki_empty_state_messages
|
||||
it 'shows the empty state message with the expected elements' do
|
||||
visit wiki_path(wiki)
|
||||
|
||||
if writable
|
||||
expect(element).to have_content("The wiki lets you write documentation for your #{container_name}")
|
||||
else
|
||||
expect(element).to have_content("This #{container_name} has no wiki pages")
|
||||
expect(element).to have_content("You must be a #{container_name} member")
|
||||
end
|
||||
|
||||
if issuable && !writable
|
||||
expect(element).to have_content("improve the wiki for this #{container_name}")
|
||||
expect(element).to have_link("issue tracker", href: project_issues_path(project))
|
||||
expect(element).to have_link("Suggest wiki improvement", href: new_project_issue_path(project))
|
||||
else
|
||||
expect(element).not_to have_content("improve the wiki for this #{container_name}")
|
||||
expect(element).not_to have_link("issue tracker")
|
||||
expect(element).not_to have_link("Suggest wiki improvement")
|
||||
end
|
||||
|
||||
if confluence
|
||||
expect(element).to have_link(confluence_link)
|
||||
else
|
||||
expect(element).not_to have_link(confluence_link)
|
||||
end
|
||||
|
||||
if writable
|
||||
element.click_link 'Create your first page'
|
||||
|
||||
expect(page).to have_button('Create page')
|
||||
else
|
||||
expect(element).not_to have_link('Create your first page')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
# Requires a context containing:
|
||||
# wiki
|
||||
# user
|
||||
|
||||
RSpec.describe 'User views a wiki page' do
|
||||
RSpec.shared_examples 'User views a wiki page' do
|
||||
include WikiHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
|
||||
let(:path) { 'image.png' }
|
||||
let(:wiki) { project.wiki }
|
||||
let(:wiki_page) do
|
||||
create(:wiki_page,
|
||||
wiki: wiki,
|
||||
|
|
@ -16,13 +15,12 @@ RSpec.describe 'User views a wiki page' do
|
|||
end
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when wiki is empty', :js do
|
||||
before do
|
||||
visit project_wikis_path(project)
|
||||
visit wiki_path(wiki)
|
||||
|
||||
wait_for_svg_to_be_loaded
|
||||
|
||||
|
|
@ -83,7 +81,7 @@ RSpec.describe 'User views a wiki page' do
|
|||
|
||||
context 'when a page does not have history' do
|
||||
before do
|
||||
visit(project_wiki_path(project, wiki_page))
|
||||
visit(wiki_page_path(wiki, wiki_page))
|
||||
end
|
||||
|
||||
it 'shows all the pages' do
|
||||
|
|
@ -92,7 +90,7 @@ RSpec.describe 'User views a wiki page' do
|
|||
end
|
||||
|
||||
context 'shows a file stored in a page' do
|
||||
let(:path) { upload_file_to_wiki(project, user, 'dk.png') }
|
||||
let(:path) { upload_file_to_wiki(wiki, user, 'dk.png') }
|
||||
|
||||
it do
|
||||
expect(page).to have_xpath("//img[@data-src='#{wiki.wiki_base_path}/#{path}']")
|
||||
|
|
@ -121,7 +119,7 @@ RSpec.describe 'User views a wiki page' do
|
|||
end
|
||||
|
||||
it 'shows the page history' do
|
||||
visit(project_wiki_path(project, wiki_page))
|
||||
visit(wiki_page_path(wiki, wiki_page))
|
||||
|
||||
expect(page).to have_selector('a.btn', text: 'Edit')
|
||||
|
||||
|
|
@ -133,12 +131,16 @@ RSpec.describe 'User views a wiki page' do
|
|||
end
|
||||
|
||||
it 'does not show the "Edit" button' do
|
||||
visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id))
|
||||
visit(wiki_page_path(wiki, wiki_page, version_id: wiki_page.versions.last.id))
|
||||
|
||||
expect(page).not_to have_selector('a.btn', text: 'Edit')
|
||||
end
|
||||
|
||||
context 'show the diff' do
|
||||
before do
|
||||
skip('Diffing for group wikis will be implemented in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42610') if wiki.container.is_a?(Group)
|
||||
end
|
||||
|
||||
def expect_diff_links(commit)
|
||||
diff_path = wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
|
||||
|
||||
|
|
@ -150,7 +152,7 @@ RSpec.describe 'User views a wiki page' do
|
|||
end
|
||||
|
||||
it 'links to the correct diffs' do
|
||||
visit project_wiki_history_path(project, wiki_page)
|
||||
visit wiki_page_path(wiki, wiki_page, action: :history)
|
||||
|
||||
commit1 = wiki.commit('HEAD^')
|
||||
commit2 = wiki.commit
|
||||
|
|
@ -208,7 +210,7 @@ RSpec.describe 'User views a wiki page' do
|
|||
end
|
||||
|
||||
it 'preserves the special characters' do
|
||||
visit(project_wiki_path(project, wiki_page))
|
||||
visit(wiki_page_path(wiki, wiki_page))
|
||||
|
||||
expect(page).to have_css('.wiki-page-title', text: title)
|
||||
expect(page).to have_css('.wiki-pages li', text: title)
|
||||
|
|
@ -223,7 +225,7 @@ RSpec.describe 'User views a wiki page' do
|
|||
end
|
||||
|
||||
it 'safely displays the page' do
|
||||
visit(project_wiki_path(project, wiki_page))
|
||||
visit(wiki_page_path(wiki, wiki_page))
|
||||
|
||||
expect(page).to have_css('.wiki-page-title', text: title)
|
||||
expect(page).to have_content('foo bar')
|
||||
|
|
@ -236,7 +238,7 @@ RSpec.describe 'User views a wiki page' do
|
|||
end
|
||||
|
||||
it 'safely displays the message' do
|
||||
visit(project_wiki_history_path(project, wiki_page))
|
||||
visit(wiki_page_path(wiki, wiki_page, action: :history))
|
||||
|
||||
expect(page).to have_content('<script>alert(true)<script>')
|
||||
end
|
||||
|
|
@ -248,7 +250,7 @@ RSpec.describe 'User views a wiki page' do
|
|||
before do
|
||||
allow(Gitlab::EncodingHelper).to receive(:encode!).and_return(content)
|
||||
|
||||
visit(project_wiki_path(project, wiki_page))
|
||||
visit(wiki_page_path(wiki, wiki_page))
|
||||
end
|
||||
|
||||
it 'does not show "Edit" button' do
|
||||
|
|
@ -263,7 +265,7 @@ RSpec.describe 'User views a wiki page' do
|
|||
end
|
||||
|
||||
it 'opens a default wiki page', :js do
|
||||
visit project_path(project)
|
||||
visit wiki.container.web_url
|
||||
|
||||
find('.shortcuts-wiki').click
|
||||
|
||||
|
|
@ -1,23 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
# Requires a context containing:
|
||||
# wiki
|
||||
# user
|
||||
|
||||
RSpec.describe 'User views wiki pages' do
|
||||
RSpec.shared_examples 'User views wiki pages' do
|
||||
include WikiHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
|
||||
|
||||
let!(:wiki_page1) do
|
||||
create(:wiki_page, wiki: project.wiki, title: '3 home', content: '3')
|
||||
create(:wiki_page, wiki: wiki, title: '3 home', content: '3')
|
||||
end
|
||||
|
||||
let!(:wiki_page2) do
|
||||
create(:wiki_page, wiki: project.wiki, title: '1 home', content: '1')
|
||||
create(:wiki_page, wiki: wiki, title: '1 home', content: '1')
|
||||
end
|
||||
|
||||
let!(:wiki_page3) do
|
||||
create(:wiki_page, wiki: project.wiki, title: '2 home', content: '2')
|
||||
create(:wiki_page, wiki: wiki, title: '2 home', content: '2')
|
||||
end
|
||||
|
||||
let(:pages) do
|
||||
|
|
@ -25,9 +24,8 @@ RSpec.describe 'User views wiki pages' do
|
|||
end
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
visit(project_wikis_pages_path(project))
|
||||
visit(wiki_path(wiki, action: :pages))
|
||||
end
|
||||
|
||||
context 'ordered by title' do
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Requires a context containing:
|
||||
# wiki
|
||||
# user
|
||||
|
||||
RSpec.shared_examples 'User views wiki sidebar' do
|
||||
include WikiHelpers
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when there are some existing pages' do
|
||||
before do
|
||||
create(:wiki_page, wiki: wiki, title: 'home', content: 'home')
|
||||
create(:wiki_page, wiki: wiki, title: 'another', content: 'another')
|
||||
end
|
||||
|
||||
it 'renders a default sidebar when there is no customized sidebar' do
|
||||
visit wiki_path(wiki)
|
||||
|
||||
expect(page).to have_content('another')
|
||||
expect(page).not_to have_link('View All Pages')
|
||||
end
|
||||
|
||||
context 'when there is a customized sidebar' do
|
||||
before do
|
||||
create(:wiki_page, wiki: wiki, title: '_sidebar', content: 'My customized sidebar')
|
||||
end
|
||||
|
||||
it 'renders my customized sidebar instead of the default one' do
|
||||
visit wiki_path(wiki)
|
||||
|
||||
expect(page).to have_content('My customized sidebar')
|
||||
expect(page).not_to have_content('Another')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are 15 existing pages' do
|
||||
before do
|
||||
(1..5).each { |i| create(:wiki_page, wiki: wiki, title: "my page #{i}") }
|
||||
(6..10).each { |i| create(:wiki_page, wiki: wiki, title: "parent/my page #{i}") }
|
||||
(11..15).each { |i| create(:wiki_page, wiki: wiki, title: "grandparent/parent/my page #{i}") }
|
||||
end
|
||||
|
||||
it 'shows all pages in the sidebar' do
|
||||
visit wiki_path(wiki)
|
||||
|
||||
(1..15).each { |i| expect(page).to have_content("my page #{i}") }
|
||||
expect(page).not_to have_link('View All Pages')
|
||||
end
|
||||
|
||||
context 'when there are more than 15 existing pages' do
|
||||
before do
|
||||
create(:wiki_page, wiki: wiki, title: 'my page 16')
|
||||
end
|
||||
|
||||
it 'shows the first 15 pages in the sidebar' do
|
||||
visit wiki_path(wiki)
|
||||
|
||||
expect(page).to have_text('my page', count: 15)
|
||||
expect(page).to have_link('View All Pages')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue