Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-29 09:09:49 +00:00
parent cd54f7e81b
commit 58d68e313f
52 changed files with 1309 additions and 1130 deletions

View File

@ -413,7 +413,7 @@ group :test do
gem 'shoulda-matchers', '~> 4.0.1', require: false gem 'shoulda-matchers', '~> 4.0.1', require: false
gem 'email_spec', '~> 2.2.0' gem 'email_spec', '~> 2.2.0'
gem 'webmock', '~> 3.5.1' gem 'webmock', '~> 3.9.1'
gem 'rails-controller-testing' gem 'rails-controller-testing'
gem 'concurrent-ruby', '~> 1.1' gem 'concurrent-ruby', '~> 1.1'
gem 'test-prof', '~> 0.12.0' gem 'test-prof', '~> 0.12.0'

View File

@ -547,7 +547,7 @@ GEM
tilt tilt
hana (1.3.6) hana (1.3.6)
hangouts-chat (0.0.5) hangouts-chat (0.0.5)
hashdiff (0.3.8) hashdiff (1.0.1)
hashie (3.6.0) hashie (3.6.0)
hashie-forbidden_attributes (0.1.1) hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0) hashie (>= 3.0)
@ -1214,10 +1214,10 @@ GEM
webfinger (1.1.0) webfinger (1.1.0)
activesupport activesupport
httpclient (>= 2.4) httpclient (>= 2.4)
webmock (3.5.1) webmock (3.9.1)
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff hashdiff (>= 0.4.0, < 2.0.0)
websocket-driver (0.7.1) websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
@ -1496,7 +1496,7 @@ DEPENDENCIES
version_sorter (~> 2.2.4) version_sorter (~> 2.2.4)
vmstat (~> 2.3.0) vmstat (~> 2.3.0)
webauthn (~> 2.3) webauthn (~> 2.3)
webmock (~> 3.5.1) webmock (~> 3.9.1)
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
yajl-ruby (~> 1.4.1) yajl-ruby (~> 1.4.1)

View File

@ -6,6 +6,7 @@ import UsersSelect from './users_select';
export default class IssuableContext { export default class IssuableContext {
constructor(currentUser) { constructor(currentUser) {
this.userSelect = new UsersSelect(currentUser); this.userSelect = new UsersSelect(currentUser);
this.reviewersSelect = new UsersSelect(currentUser, '.js-reviewer-search');
import(/* webpackChunkName: 'select2' */ 'select2/select2') import(/* webpackChunkName: 'select2' */ 'select2/select2')
.then(() => { .then(() => {

View File

@ -40,6 +40,17 @@ export default class SidebarMediator {
return this.service.update(field, data); 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) { setMoveToProjectId(projectId) {
this.store.setMoveToProjectId(projectId); this.store.setMoveToProjectId(projectId);
} }
@ -55,6 +66,7 @@ export default class SidebarMediator {
processFetchedData(data) { processFetchedData(data) {
this.store.setAssigneeData(data); this.store.setAssigneeData(data);
this.store.setReviewerData(data);
this.store.setTimeTrackingData(data); this.store.setTimeTrackingData(data);
this.store.setParticipantsData(data); this.store.setParticipantsData(data);
this.store.setSubscriptionsData(data); this.store.setSubscriptionsData(data);

View File

@ -18,8 +18,10 @@ export default class SidebarStore {
this.humanTimeSpent = ''; this.humanTimeSpent = '';
this.timeTrackingLimitToHours = timeTrackingLimitToHours; this.timeTrackingLimitToHours = timeTrackingLimitToHours;
this.assignees = []; this.assignees = [];
this.reviewers = [];
this.isFetching = { this.isFetching = {
assignees: true, assignees: true,
reviewers: true,
participants: true, participants: true,
subscriptions: 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) { setTimeTrackingData(data) {
this.timeEstimate = data.time_estimate; this.timeEstimate = data.time_estimate;
this.totalTimeSpent = data.total_time_spent; 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) { findAssignee(findAssignee) {
return this.assignees.find(assignee => assignee.id === findAssignee.id); return this.assignees.find(assignee => assignee.id === findAssignee.id);
} }
findReviewer(findReviewer) {
return this.reviewers.find(reviewer => reviewer.id === findReviewer.id);
}
removeAssignee(removeAssignee) { removeAssignee(removeAssignee) {
if (removeAssignee) { if (removeAssignee) {
this.assignees = this.assignees.filter(assignee => assignee.id !== removeAssignee.id); 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() { removeAllAssignees() {
this.assignees = []; this.assignees = [];
} }
removeAllReviewers() {
this.reviewers = [];
}
setAssigneesFromRealtime(data) { setAssigneesFromRealtime(data) {
this.assignees = data; this.assignees = data;
} }

View File

@ -19,6 +19,7 @@ import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
window.emitSidebarEvent = window.emitSidebarEvent || $.noop; window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
function UsersSelect(currentUser, els, options = {}) { function UsersSelect(currentUser, els, options = {}) {
const elsClassName = els?.toString().match('.(.+$)')[1];
const $els = $(els || '.js-user-search'); const $els = $(els || '.js-user-search');
this.users = this.users.bind(this); this.users = this.users.bind(this);
this.user = this.user.bind(this); this.user = this.user.bind(this);
@ -127,11 +128,18 @@ function UsersSelect(currentUser, els, options = {}) {
.find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`); .find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`);
firstSelected.remove(); firstSelected.remove();
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.removeReviewer', {
id: firstSelectedId,
});
} else {
emitSidebarEvent('sidebar.removeAssignee', { emitSidebarEvent('sidebar.removeAssignee', {
id: firstSelectedId, id: firstSelectedId,
}); });
} }
} }
}
}; };
const getMultiSelectDropdownTitle = function(selectedUser, isSelected) { const getMultiSelectDropdownTitle = function(selectedUser, isSelected) {
@ -392,8 +400,12 @@ function UsersSelect(currentUser, els, options = {}) {
defaultLabel, defaultLabel,
hidden() { hidden() {
if ($dropdown.hasClass('js-multiselect')) { if ($dropdown.hasClass('js-multiselect')) {
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.saveReviewers');
} else {
emitSidebarEvent('sidebar.saveAssignees'); emitSidebarEvent('sidebar.saveAssignees');
} }
}
if (!$dropdown.data('alwaysShowSelectbox')) { if (!$dropdown.data('alwaysShowSelectbox')) {
$selectbox.hide(); $selectbox.hide();
@ -428,10 +440,18 @@ function UsersSelect(currentUser, els, options = {}) {
previouslySelected.each((index, element) => { previouslySelected.each((index, element) => {
element.remove(); element.remove();
}); });
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.removeAllReviewers');
} else {
emitSidebarEvent('sidebar.removeAllAssignees'); emitSidebarEvent('sidebar.removeAllAssignees');
}
} else if (isActive) { } else if (isActive) {
// user selected // user selected
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.addReviewer', user);
} else {
emitSidebarEvent('sidebar.addAssignee', user); emitSidebarEvent('sidebar.addAssignee', user);
}
// Remove unassigned selection (if it was previously selected) // Remove unassigned selection (if it was previously selected)
const unassignedSelected = $dropdown const unassignedSelected = $dropdown
@ -448,8 +468,12 @@ function UsersSelect(currentUser, els, options = {}) {
} }
// User unselected // User unselected
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.removeReviewer', user);
} else {
emitSidebarEvent('sidebar.removeAssignee', user); emitSidebarEvent('sidebar.removeAssignee', user);
} }
}
if (getSelected().find(u => u === gon.current_user_id)) { if (getSelected().find(u => u === gon.current_user_id)) {
$assignToMeLink.hide(); $assignToMeLink.hide();

View File

@ -139,7 +139,7 @@ export default {
<template> <template>
<div class="branch-commit cgray"> <div class="branch-commit cgray">
<template v-if="shouldShowRefInfo"> <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-if="tag" name="tag" />
<gl-icon v-else-if="mergeRequestRef" name="git-merge" /> <gl-icon v-else-if="mergeRequestRef" name="git-merge" />
<gl-icon v-else name="branch" /> <gl-icon v-else name="branch" />

View File

@ -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>

View File

@ -4,6 +4,7 @@ import { GlTable } from '@gitlab/ui';
import { FIELDS } from '../constants'; import { FIELDS } from '../constants';
import initUserPopovers from '~/user_popovers'; import initUserPopovers from '~/user_popovers';
import MemberAvatar from './member_avatar.vue'; import MemberAvatar from './member_avatar.vue';
import MemberSource from './member_source.vue';
import MembersTableCell from './members_table_cell.vue'; import MembersTableCell from './members_table_cell.vue';
export default { export default {
@ -12,6 +13,7 @@ export default {
GlTable, GlTable,
MemberAvatar, MemberAvatar,
MembersTableCell, MembersTableCell,
MemberSource,
}, },
computed: { computed: {
...mapState(['members', 'tableFields']), ...mapState(['members', 'tableFields']),
@ -43,8 +45,10 @@ export default {
</members-table-cell> </members-table-cell>
</template> </template>
<template #cell(source)> <template #cell(source)="{ item: member }">
<!-- Temporarily empty --> <members-table-cell #default="{ isDirectMember }" :member="member">
<member-source :is-direct-member="isDirectMember" :member-source="member.source" />
</members-table-cell>
</template> </template>
<template #head(actions)="{ label }"> <template #head(actions)="{ label }">

View File

@ -1,4 +1,5 @@
<script> <script>
import { mapState } from 'vuex';
import { MEMBER_TYPES } from '../constants'; import { MEMBER_TYPES } from '../constants';
export default { export default {
@ -10,6 +11,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['sourceId']),
isGroup() { isGroup() {
return Boolean(this.member.sharedWithGroup); return Boolean(this.member.sharedWithGroup);
}, },
@ -30,10 +32,14 @@ export default {
return MEMBER_TYPES.user; return MEMBER_TYPES.user;
}, },
isDirectMember() {
return this.member.source?.id === this.sourceId;
},
}, },
render() { render() {
return this.$scopedSlots.default({ return this.$scopedSlots.default({
memberType: this.memberType, memberType: this.memberType,
isDirectMember: this.isDirectMember,
}); });
}, },
}; };

View File

@ -111,6 +111,10 @@
white-space: nowrap; white-space: nowrap;
} }
} }
.pipeline-tags .label-container {
white-space: normal;
}
} }
} }
@ -124,22 +128,6 @@
} }
.ci-table { .ci-table {
.build.retried {
background-color: $gray-lightest;
}
.commit-link {
a {
&:focus {
text-decoration: none;
}
}
a:hover {
text-decoration: none;
}
}
.avatar { .avatar {
margin-left: 0; margin-left: 0;
float: none; 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, .duration,
.finished-at { .finished-at {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
margin: 0; margin: 0;
white-space: nowrap; white-space: nowrap;
.fa {
font-size: 12px;
margin-right: 4px;
}
svg { svg {
width: 12px; width: 12px;
height: 12px; height: 12px;
@ -241,14 +196,6 @@
.build-link a { .build-link a {
color: $gl-text-color; color: $gl-text-color;
} }
.btn-group.open .dropdown-toggle {
box-shadow: none;
}
.pipeline-tags .label-container {
white-space: normal;
}
} }
.stage-cell { .stage-cell {

View File

@ -386,6 +386,12 @@ module IssuablesHelper
end end
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) def issuable_squash_option?(issuable, project)
if issuable.persisted? if issuable.persisted?
issuable.squash issuable.squash

View File

@ -10,7 +10,7 @@
%span.build-link ##{artifact.job_id} %span.build-link ##{artifact.job_id}
- if artifact.job.ref - 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') = 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' = link_to artifact.job.ref, project_ref_path(@project, artifact.job.ref), class: 'ref-name'
- else - else

View File

@ -3,14 +3,14 @@
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3 .count-badge.d-inline-flex.align-item-stretch.gl-mr-3
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - 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 = 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') %span= s_('ProjectOverview|Fork')
- else - else
- can_create_fork = current_user.can?(:create_fork) - can_create_fork = current_user.can?(:create_fork)
= link_to new_project_fork_path(@project), = 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}", 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 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= s_('ProjectOverview|Fork')
%span.fork-count.count-badge-count.d-flex.align-items-center %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 = link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do

View File

@ -2,10 +2,10 @@
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3 .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) } } %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) - if current_user.starred?(@project)
= sprite_icon('star', { css_class: 'icon' }) = sprite_icon('star', css_class: 'icon')
%span.starred= s_('ProjectOverview|Unstar') %span.starred= s_('ProjectOverview|Unstar')
- else - else
= sprite_icon('star-o', { css_class: 'icon' }) = sprite_icon('star-o', css_class: 'icon')
%span= s_('ProjectOverview|Star') %span= s_('ProjectOverview|Star')
%span.star-count.count-badge-count.d-flex.align-items-center %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 = 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 - else
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3 .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 = 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= s_('ProjectOverview|Star')
%span.star-count.count-badge-count.d-flex.align-items-center %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 = link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do

View File

@ -21,7 +21,7 @@
- if ref - if ref
- if job.ref - if job.ref
.icon-container .icon-container.gl-display-inline-block
= job.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite') = job.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
= link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name" = link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name"
- else - else

View File

@ -1,7 +1,7 @@
.table-mobile-content .table-mobile-content
.branch-commit.cgray .branch-commit.cgray
- if deployment.ref - if deployment.ref
%span.icon-container %span.icon-container.gl-display-inline-block
= deployment.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite') = deployment.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
= link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name" = link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
.icon-container.commit-icon .icon-container.commit-icon

View File

@ -92,7 +92,7 @@
.loading.hide .loading.hide
.spinner.spinner-md .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) - if @merge_request.can_be_reverted?(current_user)
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title

View File

@ -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)

View File

@ -1,7 +1,7 @@
--- ---
name: broadcast_issue_updates name: broadcast_issue_updates
introduced_by_url: introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30732
rollout_issue_url: rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1210
group: group: group::project management
type: development type: development
default_enabled: false default_enabled: false

View File

@ -1,7 +1,7 @@
--- ---
name: dashboard_pipeline_status name: dashboard_pipeline_status
introduced_by_url: introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22029
rollout_issue_url: rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/209061
group: group: group::continuous integration
type: development type: development
default_enabled: true default_enabled: true

View File

@ -1,7 +1,7 @@
--- ---
name: real_time_issue_sidebar name: real_time_issue_sidebar
introduced_by_url: introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30239
rollout_issue_url: rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1210
group: group: group::project management
type: development type: development
default_enabled: false default_enabled: false

View File

@ -647,7 +647,7 @@ tables: `namespaces`. This can be translated to:
```sql ```sql
ALTER TABLE namespaces ALTER TABLE namespaces
ALTER COLUMN request_access_enabled 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 In this particular case, the default value exists and we're just changing the metadata for

View File

@ -674,11 +674,6 @@ To delete an existing site profile:
## Scanner profile ## Scanner profile
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222767) in GitLab 13.4. > - [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: 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 **Manage** in the **DAST Profiles** row.
1. Click **{remove}** in the scanner profile's 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 ## On-demand scans
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218465) in GitLab 13.2. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218465) in GitLab 13.2.

View File

@ -8934,6 +8934,9 @@ msgstr ""
msgid "Diffs|Something went wrong while fetching diff lines." msgid "Diffs|Something went wrong while fetching diff lines."
msgstr "" msgstr ""
msgid "Direct member"
msgstr ""
msgid "Direction" msgid "Direction"
msgstr "" 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}." msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
msgstr "" msgstr ""
msgid "Inherited"
msgstr ""
msgid "Inherited:" msgid "Inherited:"
msgstr "" msgstr ""
@ -17673,12 +17679,6 @@ msgstr ""
msgid "On track" msgid "On track"
msgstr "" 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." msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later."
msgstr "" 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}" msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
msgstr "" msgstr ""
msgid "OnDemandScans|Only a passive scan can be performed on demand."
msgstr ""
msgid "OnDemandScans|Passive"
msgstr ""
msgid "OnDemandScans|Run scan" msgid "OnDemandScans|Run scan"
msgstr "" msgstr ""
msgid "OnDemandScans|Scan mode"
msgstr ""
msgid "OnDemandScans|Scanner profile" msgid "OnDemandScans|Scanner profile"
msgstr "" msgstr ""
@ -21668,6 +21659,9 @@ msgstr ""
msgid "Request parameter %{param} is missing." msgid "Request parameter %{param} is missing."
msgstr "" msgstr ""
msgid "Request review from"
msgstr ""
msgid "Request to link SAML account must be authorized" msgid "Request to link SAML account must be authorized"
msgstr "" msgstr ""
@ -21938,6 +21932,9 @@ msgstr ""
msgid "ReviewApp|Enable Review App" msgid "ReviewApp|Enable Review App"
msgstr "" msgstr ""
msgid "Reviewer"
msgstr ""
msgid "Reviewing" msgid "Reviewing"
msgstr "" msgstr ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -2,108 +2,86 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'User views empty wiki' do RSpec.describe 'Project > User views empty wiki' do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:confluence_link) { 'Enable the Confluence Wiki integration' }
let(:element) { page.find('.row.empty-state') }
shared_examples 'empty wiki and accessible issues' do let(:wiki) { create(:project_wiki, project: project) }
it 'show "issue tracker" message' do
visit(project_wikis_path(project))
expect(element).to have_content('This project has no wiki pages') it_behaves_like 'User views empty wiki' do
expect(element).to have_content('You must be a project member') context 'when project is public' do
expect(element).to have_content('improve the wiki for this project') let(:project) { create(:project, :public) }
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
shared_examples 'empty wiki and non-accessible issues' do it_behaves_like 'empty wiki message', issuable: true
it 'does not show "issue tracker" message' do
visit(project_wikis_path(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_no_link('Suggest wiki improvement')
expect(element).to have_no_link(confluence_link)
end
end
context 'when user is logged out and issue tracker is public' do
let(:project) { create(:project, :public, :wiki_repo) }
it_behaves_like 'empty wiki and accessible issues'
end
context 'when user is logged in and not a member' do
let(:project) { create(:project, :public, :wiki_repo) }
before do
sign_in(user)
end
it_behaves_like 'empty wiki and accessible issues'
end
context 'when issue tracker is private' do context 'when issue tracker is private' do
let(:project) { create(:project, :public, :wiki_repo, :issues_private) } let(:project) { create(:project, :public, :issues_private) }
it_behaves_like 'empty wiki and non-accessible issues' it_behaves_like 'empty wiki message', issuable: false
end end
context 'when issue tracker is disabled' do context 'when issue tracker is disabled' do
let(:project) { create(:project, :public, :wiki_repo, :issues_disabled) } let(:project) { create(:project, :public, :issues_disabled) }
it_behaves_like 'empty wiki and non-accessible issues' it_behaves_like 'empty wiki message', issuable: false
end end
context 'when user is logged in and a member' do context 'and user is logged in' do
let(:project) { create(:project, :public) }
before do before do
sign_in(user) 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) project.add_developer(user)
end end
it 'shows "create first page" message' do it_behaves_like 'empty wiki message', writable: true, issuable: true
visit(project_wikis_path(project))
expect(element).to have_content('your project', count: 2)
element.click_link 'Create your first page'
expect(page).to have_button('Create page')
end 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
end end
context 'when user is logged in and an admin' do context 'when project is private' do
let(:project) { create(:project, :public, :wiki_repo) } let(:project) { create(:project, :private) }
it_behaves_like 'wiki is not found'
context 'and user is logged in' do
before do before do
sign_in(user) sign_in(user)
end
context 'and user is not a member' do
it_behaves_like 'wiki is not found'
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
context 'and user is a maintainer' do
before do
project.add_maintainer(user) project.add_maintainer(user)
end end
it 'shows the "enable confluence" button' do it_behaves_like 'empty wiki message', writable: true, issuable: true, confluence: true
visit(project_wikis_path(project))
expect(element).to have_link(confluence_link) context 'and Confluence is already enabled' do
before do
create(:confluence_service, project: project)
end end
it 'does not show "enable confluence" button if confluence is already enabled' do it_behaves_like 'empty wiki message', writable: true, issuable: true, confluence: false
create(:confluence_service, project: project) end
end
visit(project_wikis_path(project)) end
expect(element).to have_no_link(confluence_link)
end end
end end
end end

View File

@ -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

View File

@ -3,6 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Releases (JavaScript fixtures)' do RSpec.describe 'Releases (JavaScript fixtures)' do
include ApiHelpers
include JavaScriptFixturesHelpers include JavaScriptFixturesHelpers
let_it_be(:admin) { create(:admin) } let_it_be(:admin) { create(:admin) }
@ -68,16 +69,14 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
link_type: :runbook) link_type: :runbook)
end end
before(:all) do
clean_frontend_fixtures('api/releases/')
end
after(:all) do after(:all) do
remove_repository(project) remove_repository(project)
end end
describe API::Releases, '(JavaScript fixtures)', type: :request do describe API::Releases, type: :request do
include ApiHelpers before(:all) do
clean_frontend_fixtures('api/releases/')
end
it 'api/releases/release.json' do it 'api/releases/release.json' do
get api("/projects/#{project.id}/releases/#{release.tag}", admin) get api("/projects/#{project.id}/releases/#{release.tag}", admin)
@ -85,4 +84,22 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
expect(response).to be_successful expect(response).to be_successful
end end
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 end

View File

@ -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');
});
});
});

View File

@ -1,4 +1,5 @@
import { mount, createLocalVue } from '@vue/test-utils'; import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { MEMBER_TYPES } from '~/vue_shared/components/members/constants'; import { MEMBER_TYPES } from '~/vue_shared/components/members/constants';
import { member as memberMock, group, invite, accessRequest } from '../mock_data'; import { member as memberMock, group, invite, accessRequest } from '../mock_data';
import MembersTableCell from '~/vue_shared/components/members/table/members_table_cell.vue'; import MembersTableCell from '~/vue_shared/components/members/table/members_table_cell.vue';
@ -10,6 +11,10 @@ describe('MemberList', () => {
type: String, type: String,
required: true, required: true,
}, },
isDirectMember: {
type: Boolean,
required: true,
},
}, },
render(createElement) { render(createElement) {
return createElement('div', this.memberType); return createElement('div', this.memberType);
@ -17,20 +22,34 @@ describe('MemberList', () => {
}; };
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex);
localVue.component('wrapped-component', WrappedComponent); localVue.component('wrapped-component', WrappedComponent);
let wrapper; const createStore = (state = {}) => {
return new Vuex.Store({
const createComponent = propsData => { state: {
wrapper = mount(MembersTableCell, { sourceId: 1,
localVue, ...state,
propsData,
scopedSlots: {
default: '<wrapped-component :member-type="props.memberType" />',
}, },
}); });
}; };
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(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
@ -47,7 +66,31 @@ describe('MemberList', () => {
({ member, expectedMemberType }) => { ({ member, expectedMemberType }) => {
createComponent({ member }); 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);
});
});
}); });

View File

@ -5,7 +5,10 @@ import {
getByTestId as getByTestIdHelper, getByTestId as getByTestIdHelper,
} from '@testing-library/dom'; } from '@testing-library/dom';
import MembersTable from '~/vue_shared/components/members/table/members_table.vue'; 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 * as initUserPopovers from '~/user_popovers';
import { member as memberMock, invite, accessRequest } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
@ -44,20 +47,31 @@ describe('MemberList', () => {
describe('fields', () => { describe('fields', () => {
it.each` it.each`
field | label field | label | member | expectedComponent
${'source'} | ${'Source'} ${'account'} | ${'Account'} | ${memberMock} | ${MemberAvatar}
${'granted'} | ${'Access granted'} ${'source'} | ${'Source'} | ${memberMock} | ${MemberSource}
${'invited'} | ${'Invited'} ${'granted'} | ${'Access granted'} | ${memberMock} | ${null}
${'requested'} | ${'Requested'} ${'invited'} | ${'Invited'} | ${invite} | ${null}
${'expires'} | ${'Access expires'} ${'requested'} | ${'Requested'} | ${accessRequest} | ${null}
${'maxRole'} | ${'Max role'} ${'expires'} | ${'Access expires'} | ${memberMock} | ${null}
${'expiration'} | ${'Expiration'} ${'maxRole'} | ${'Max role'} | ${memberMock} | ${null}
`('renders the $label field', ({ field, label }) => { ${'expiration'} | ${'Expiration'} | ${memberMock} | ${null}
`('renders the $label field', ({ field, label, member, expectedComponent }) => {
createComponent({ createComponent({
members: [member],
tableFields: [field], tableFields: [field],
}); });
expect(getByText(label, { selector: '[role="columnheader"]' }).exists()).toBe(true); 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', () => { it('renders "Actions" field for screen readers', () => {

View File

@ -306,6 +306,38 @@ RSpec.describe IssuablesHelper do
end end
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 describe '#issuable_squash_option?' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax

View File

@ -41,11 +41,11 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
context 'for web IDE edit actions' do context 'for web IDE edit actions' do
it_behaves_like 'tracks and counts action' do it_behaves_like 'tracks and counts action' do
def track_action(params) def track_action(params)
described_class.track_web_ide_edit_action(params) described_class.track_web_ide_edit_action(**params)
end end
def count_unique(params) def count_unique(params)
described_class.count_web_ide_edit_actions(params) described_class.count_web_ide_edit_actions(**params)
end end
end end
end end
@ -53,11 +53,11 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
context 'for SFE edit actions' do context 'for SFE edit actions' do
it_behaves_like 'tracks and counts action' do it_behaves_like 'tracks and counts action' do
def track_action(params) def track_action(params)
described_class.track_sfe_edit_action(params) described_class.track_sfe_edit_action(**params)
end end
def count_unique(params) def count_unique(params)
described_class.count_sfe_edit_actions(params) described_class.count_sfe_edit_actions(**params)
end end
end end
end end
@ -65,11 +65,11 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
context 'for snippet editor edit actions' do context 'for snippet editor edit actions' do
it_behaves_like 'tracks and counts action' do it_behaves_like 'tracks and counts action' do
def track_action(params) def track_action(params)
described_class.track_snippet_editor_edit_action(params) described_class.track_snippet_editor_edit_action(**params)
end end
def count_unique(params) def count_unique(params)
described_class.count_snippet_editor_edit_actions(params) described_class.count_snippet_editor_edit_actions(**params)
end end
end end
end end

View File

@ -8,11 +8,11 @@ RSpec.describe Gitlab::UsageDataCounters::TrackUniqueEvents, :clean_gitlab_redis
let(:time) { Time.zone.now } let(:time) { Time.zone.now }
def track_event(params) def track_event(params)
track_unique_events.track_event(params) track_unique_events.track_event(**params)
end end
def count_unique(params) def count_unique(params)
track_unique_events.count_unique_events(params) track_unique_events.count_unique_events(**params)
end end
context 'tracking an event' do context 'tracking an event' do

View File

@ -13,16 +13,16 @@ module WikiHelpers
find('.svg-content .js-lazy-loaded') if example.nil? || example.metadata.key?(:js) find('.svg-content .js-lazy-loaded') if example.nil? || example.metadata.key?(:js)
end end
def upload_file_to_wiki(container, user, file_name) def upload_file_to_wiki(wiki, user, file_name)
opts = { params = {
file_name: file_name, file_name: file_name,
file_content: File.read(expand_fixture_path(file_name)) file_content: File.read(expand_fixture_path(file_name))
} }
::Wikis::CreateAttachmentService.new( ::Wikis::CreateAttachmentService.new(
container: container, container: wiki.container,
current_user: user, current_user: user,
params: opts params: params
).execute[:result][:file_path] ).execute.dig(:result, :file_path)
end end
end end

View File

@ -226,7 +226,7 @@ RSpec.shared_examples 'wiki controller actions' do
where(:file_name) { ['dk.png', 'unsanitized.svg', 'git-cheat-sheet.pdf'] } where(:file_name) { ['dk.png', 'unsanitized.svg', 'git-cheat-sheet.pdf'] }
with_them do 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 it 'delivers the file with the correct headers' do
subject subject

View File

@ -1,14 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
# Requires a context containing: # Requires a context containing:
# project # wiki
RSpec.shared_examples 'wiki file attachments' do RSpec.shared_examples 'wiki file attachments' do
include DropzoneHelper include DropzoneHelper
context 'uploading attachments', :js do context 'uploading attachments', :js do
let(:wiki) { project.wiki }
def attach_with_dropzone(wait = false) def attach_with_dropzone(wait = false)
dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, wait) dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, wait)
end end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,11 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' RSpec.shared_examples 'User views AsciiDoc page with includes' do
RSpec.describe 'User views AsciiDoc page with includes', :js do
let_it_be(:user) { create(:user) }
let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' } 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!(: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[]") } 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 format: :asciidoc
} }
create(:wiki_page, wiki: project.wiki, **attrs) create(:wiki_page, wiki: wiki, **attrs)
end end
before do before do
sign_in(user) sign_in(user)
end end
context 'when the file being included exists' do context 'when the file being included exists', :js do
it 'includes the file contents' 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 page.within(:css, wiki_content_selector) do
expect(page).to have_content('Content from the main page. Content from the included page') 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 context 'when there are multiple versions of the wiki pages' do
before do before do
# rubocop:disable Rails/SaveBang
included_wiki_page.update(message: 'updated included file', content: 'Updated content from the included page') 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[]") wiki_page.update(message: 'updated wiki page', content: "Updated content from the main page.\ninclude::included_page.asciidoc[]")
# rubocop:enable Rails/SaveBang
end end
let(:latest_version_id) { wiki_page.versions.first.id } 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 context 'viewing the latest version' do
it 'includes the latest content' 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 page.within(:css, wiki_content_selector) do
expect(page).to have_content('Updated content from the main page. Updated content from the included page') 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 context 'viewing the original version' do
it 'includes the content from 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 page.within(:css, wiki_content_selector) do
expect(page).to have_content('Content from the main page. Content from the included page') 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
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 before do
included_wiki_page.delete included_wiki_page.delete
end end
it 'outputs an error' do 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 page.within(:css, wiki_content_selector) do
expect(page).to have_content('Content from the main page. [ERROR: include::included_page.asciidoc[] - unresolved directive]') expect(page).to have_content('Content from the main page. [ERROR: include::included_page.asciidoc[] - unresolved directive]')

View File

@ -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

View File

@ -1,14 +1,13 @@
# frozen_string_literal: true # 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 include WikiHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:path) { 'image.png' } let(:path) { 'image.png' }
let(:wiki) { project.wiki }
let(:wiki_page) do let(:wiki_page) do
create(:wiki_page, create(:wiki_page,
wiki: wiki, wiki: wiki,
@ -16,13 +15,12 @@ RSpec.describe 'User views a wiki page' do
end end
before do before do
project.add_maintainer(user)
sign_in(user) sign_in(user)
end end
context 'when wiki is empty', :js do context 'when wiki is empty', :js do
before do before do
visit project_wikis_path(project) visit wiki_path(wiki)
wait_for_svg_to_be_loaded 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 context 'when a page does not have history' do
before do before do
visit(project_wiki_path(project, wiki_page)) visit(wiki_page_path(wiki, wiki_page))
end end
it 'shows all the pages' do it 'shows all the pages' do
@ -92,7 +90,7 @@ RSpec.describe 'User views a wiki page' do
end end
context 'shows a file stored in a page' do 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 it do
expect(page).to have_xpath("//img[@data-src='#{wiki.wiki_base_path}/#{path}']") 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 end
it 'shows the page history' do 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') expect(page).to have_selector('a.btn', text: 'Edit')
@ -133,12 +131,16 @@ RSpec.describe 'User views a wiki page' do
end end
it 'does not show the "Edit" button' do 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') expect(page).not_to have_selector('a.btn', text: 'Edit')
end end
context 'show the diff' do 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) def expect_diff_links(commit)
diff_path = wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff) 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 end
it 'links to the correct diffs' do 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^') commit1 = wiki.commit('HEAD^')
commit2 = wiki.commit commit2 = wiki.commit
@ -208,7 +210,7 @@ RSpec.describe 'User views a wiki page' do
end end
it 'preserves the special characters' do 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-page-title', text: title)
expect(page).to have_css('.wiki-pages li', 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 end
it 'safely displays the page' do 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_css('.wiki-page-title', text: title)
expect(page).to have_content('foo bar') expect(page).to have_content('foo bar')
@ -236,7 +238,7 @@ RSpec.describe 'User views a wiki page' do
end end
it 'safely displays the message' do 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>') expect(page).to have_content('<script>alert(true)<script>')
end end
@ -248,7 +250,7 @@ RSpec.describe 'User views a wiki page' do
before do before do
allow(Gitlab::EncodingHelper).to receive(:encode!).and_return(content) allow(Gitlab::EncodingHelper).to receive(:encode!).and_return(content)
visit(project_wiki_path(project, wiki_page)) visit(wiki_page_path(wiki, wiki_page))
end end
it 'does not show "Edit" button' do it 'does not show "Edit" button' do
@ -263,7 +265,7 @@ RSpec.describe 'User views a wiki page' do
end end
it 'opens a default wiki page', :js do it 'opens a default wiki page', :js do
visit project_path(project) visit wiki.container.web_url
find('.shortcuts-wiki').click find('.shortcuts-wiki').click

View File

@ -1,23 +1,22 @@
# frozen_string_literal: true # 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 include WikiHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let!(:wiki_page1) do 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 end
let!(:wiki_page2) do 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 end
let!(:wiki_page3) do 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 end
let(:pages) do let(:pages) do
@ -25,9 +24,8 @@ RSpec.describe 'User views wiki pages' do
end end
before do before do
project.add_maintainer(user)
sign_in(user) sign_in(user)
visit(project_wikis_pages_path(project)) visit(wiki_path(wiki, action: :pages))
end end
context 'ordered by title' do context 'ordered by title' do

View File

@ -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