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 'email_spec', '~> 2.2.0'
gem 'webmock', '~> 3.5.1'
gem 'webmock', '~> 3.9.1'
gem 'rails-controller-testing'
gem 'concurrent-ruby', '~> 1.1'
gem 'test-prof', '~> 0.12.0'

View File

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

View File

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

View File

@ -40,6 +40,17 @@ export default class SidebarMediator {
return this.service.update(field, data);
}
saveReviewers(field) {
const selected = this.store.reviewers.map(u => u.id);
// If there are no ids, that means we have to unassign (which is id = 0)
// And it only accepts an array, hence [0]
const reviewers = selected.length === 0 ? [0] : selected;
const data = { reviewer_ids: reviewers };
return this.service.update(field, data);
}
setMoveToProjectId(projectId) {
this.store.setMoveToProjectId(projectId);
}
@ -55,6 +66,7 @@ export default class SidebarMediator {
processFetchedData(data) {
this.store.setAssigneeData(data);
this.store.setReviewerData(data);
this.store.setTimeTrackingData(data);
this.store.setParticipantsData(data);
this.store.setSubscriptionsData(data);

View File

@ -18,8 +18,10 @@ export default class SidebarStore {
this.humanTimeSpent = '';
this.timeTrackingLimitToHours = timeTrackingLimitToHours;
this.assignees = [];
this.reviewers = [];
this.isFetching = {
assignees: true,
reviewers: true,
participants: true,
subscriptions: true,
};
@ -42,6 +44,13 @@ export default class SidebarStore {
}
}
setReviewerData(data) {
this.isFetching.reviewers = false;
if (data.reviewers) {
this.reviewers = data.reviewers;
}
}
setTimeTrackingData(data) {
this.timeEstimate = data.time_estimate;
this.totalTimeSpent = data.total_time_spent;
@ -75,20 +84,40 @@ export default class SidebarStore {
}
}
addReviewer(reviewer) {
if (!this.findReviewer(reviewer)) {
this.reviewers.push(reviewer);
}
}
findAssignee(findAssignee) {
return this.assignees.find(assignee => assignee.id === findAssignee.id);
}
findReviewer(findReviewer) {
return this.reviewers.find(reviewer => reviewer.id === findReviewer.id);
}
removeAssignee(removeAssignee) {
if (removeAssignee) {
this.assignees = this.assignees.filter(assignee => assignee.id !== removeAssignee.id);
}
}
removeReviewer(removeReviewer) {
if (removeReviewer) {
this.reviewers = this.reviewers.filter(reviewer => reviewer.id !== removeReviewer.id);
}
}
removeAllAssignees() {
this.assignees = [];
}
removeAllReviewers() {
this.reviewers = [];
}
setAssigneesFromRealtime(data) {
this.assignees = data;
}

View File

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

View File

@ -139,7 +139,7 @@ export default {
<template>
<div class="branch-commit cgray">
<template v-if="shouldShowRefInfo">
<div class="icon-container">
<div class="icon-container gl-display-inline-block">
<gl-icon v-if="tag" name="tag" />
<gl-icon v-else-if="mergeRequestRef" name="git-merge" />
<gl-icon v-else name="branch" />

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

View File

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

View File

@ -111,6 +111,10 @@
white-space: nowrap;
}
}
.pipeline-tags .label-container {
white-space: normal;
}
}
}
@ -124,22 +128,6 @@
}
.ci-table {
.build.retried {
background-color: $gray-lightest;
}
.commit-link {
a {
&:focus {
text-decoration: none;
}
}
a:hover {
text-decoration: none;
}
}
.avatar {
margin-left: 0;
float: none;
@ -191,45 +179,12 @@
}
}
.icon-container {
display: inline-block;
&.commit-icon {
width: 15px;
text-align: center;
}
}
/**
* Play button with icon in dropdowns
*/
.no-btn {
border: 0;
background: none;
outline: none;
width: 100%;
text-align: left;
.icon-play {
position: relative;
top: 2px;
margin-right: 5px;
height: 13px;
width: 12px;
}
}
.duration,
.finished-at {
color: $gl-text-color-secondary;
margin: 0;
white-space: nowrap;
.fa {
font-size: 12px;
margin-right: 4px;
}
svg {
width: 12px;
height: 12px;
@ -241,14 +196,6 @@
.build-link a {
color: $gl-text-color;
}
.btn-group.open .dropdown-toggle {
box-shadow: none;
}
.pipeline-tags .label-container {
white-space: normal;
}
}
.stage-cell {

View File

@ -386,6 +386,12 @@ module IssuablesHelper
end
end
def reviewer_sidebar_data(reviewer, merge_request: nil)
{ avatar_url: reviewer.avatar_url, name: reviewer.name, username: reviewer.username }.tap do |data|
data[:can_merge] = merge_request.can_be_merged_by?(reviewer) if merge_request
end
end
def issuable_squash_option?(issuable, project)
if issuable.persisted?
issuable.squash

View File

@ -10,7 +10,7 @@
%span.build-link ##{artifact.job_id}
- if artifact.job.ref
.icon-container{ "aria-label" => artifact.job.tag? ? _('Tag') : _('Branch') }
.icon-container.gl-display-inline-block{ "aria-label" => artifact.job.tag? ? _('Tag') : _('Branch') }
= artifact.job.tag? ? sprite_icon('tag', css_class: 'sprite') : sprite_icon('branch', css_class: 'sprite')
= link_to artifact.job.ref, project_ref_path(@project, artifact.job.ref), class: 'ref-name'
- else

View File

@ -3,14 +3,14 @@
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn' do
= sprite_icon('fork', { css_class: 'icon' })
= sprite_icon('fork', css_class: 'icon')
%span= s_('ProjectOverview|Fork')
- else
- can_create_fork = current_user.can?(:create_fork)
= link_to new_project_fork_path(@project),
class: "btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center fork-btn #{'has-tooltip disabled' unless can_create_fork}",
title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do
= sprite_icon('fork', { css_class: 'icon' })
= sprite_icon('fork', css_class: 'icon')
%span= s_('ProjectOverview|Fork')
%span.fork-count.count-badge-count.d-flex.align-items-center
= link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do

View File

@ -2,10 +2,10 @@
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3
%button.count-badge-button.btn.btn-default.btn-xs.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }
- if current_user.starred?(@project)
= sprite_icon('star', { css_class: 'icon' })
= sprite_icon('star', css_class: 'icon')
%span.starred= s_('ProjectOverview|Unstar')
- else
= sprite_icon('star-o', { css_class: 'icon' })
= sprite_icon('star-o', css_class: 'icon')
%span= s_('ProjectOverview|Star')
%span.star-count.count-badge-count.d-flex.align-items-center
= link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do
@ -14,7 +14,7 @@
- else
.count-badge.d-inline-flex.align-item-stretch.gl-mr-3
= link_to new_user_session_path, class: 'btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
= sprite_icon('star-o', { css_class: 'icon' })
= sprite_icon('star-o', css_class: 'icon')
%span= s_('ProjectOverview|Star')
%span.star-count.count-badge-count.d-flex.align-items-center
= link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do

View File

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

View File

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

View File

@ -92,7 +92,7 @@
.loading.hide
.spinner.spinner-md
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, source_branch: @merge_request.source_branch
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees, reviewers: @merge_request.reviewers, source_branch: @merge_request.source_branch
- if @merge_request.can_be_reverted?(current_user)
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title

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
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30732
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1210
group: group::project management
type: development
default_enabled: false

View File

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

View File

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

View File

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

View File

@ -674,11 +674,6 @@ To delete an existing site profile:
## Scanner profile
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222767) in GitLab 13.4.
> - [Deployed behind a feature flag](../../feature_flags.md), enabled by default.
> - Enabled on GitLab.com.
> - Can be enabled or disabled per-project.
> - Recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can [disable this feature](#enable-or-disable-dast-scanner-profiles).
A scanner profile defines the scanner settings used to run an on-demand scan:
@ -713,29 +708,6 @@ To delete a scanner profile:
1. Click **Manage** in the **DAST Profiles** row.
1. Click **{remove}** in the scanner profile's row.
### Enable or disable DAST scanner profiles
The scanner profile feature is ready for production use. It's deployed behind a feature flag that
is **enabled by default**. [GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can opt to disable it.
To disable it:
```ruby
# Instance-wide
Feature.disable(:security_on_demand_scans_scanner_profiles)
# or by project
Feature.disable(:security_on_demand_scans_scanner_profiles, Project.find(<project id>))
```
To enable it:
```ruby
# Instance-wide
Feature.enable(:security_on_demand_scans_scanner_profiles)
# or by project
Feature.enable(:security_on_demand_scans_scanner_profiles, Project.find(<project ID>))
```
## On-demand scans
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218465) in GitLab 13.2.

View File

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

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

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'
RSpec.describe 'Releases (JavaScript fixtures)' do
include ApiHelpers
include JavaScriptFixturesHelpers
let_it_be(:admin) { create(:admin) }
@ -68,16 +69,14 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
link_type: :runbook)
end
before(:all) do
clean_frontend_fixtures('api/releases/')
end
after(:all) do
remove_repository(project)
end
describe API::Releases, '(JavaScript fixtures)', type: :request do
include ApiHelpers
describe API::Releases, type: :request do
before(:all) do
clean_frontend_fixtures('api/releases/')
end
it 'api/releases/release.json' do
get api("/projects/#{project.id}/releases/#{release.tag}", admin)
@ -85,4 +84,22 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
expect(response).to be_successful
end
end
graphql_query_path = 'releases/queries/all_releases.query.graphql'
describe "~/#{graphql_query_path}", type: :request do
include GraphqlHelpers
before(:all) do
clean_frontend_fixtures('graphql/releases/')
end
it "graphql/#{graphql_query_path}.json" do
query = File.read(File.join(Rails.root, '/app/assets/javascripts', graphql_query_path))
post_graphql(query, current_user: admin, variables: { fullPath: project.full_path })
expect_graphql_errors_to_be_empty
end
end
end

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

View File

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

View File

@ -306,6 +306,38 @@ RSpec.describe IssuablesHelper do
end
end
describe '#reviewer_sidebar_data' do
let(:user) { create(:user) }
subject { helper.reviewer_sidebar_data(user, merge_request: merge_request) }
context 'without merge_request' do
let(:merge_request) { nil }
it 'returns hash of reviewer data' do
is_expected.to eql({
avatar_url: user.avatar_url,
name: user.name,
username: user.username
})
end
end
context 'with merge_request' do
let(:merge_request) { build(:merge_request) }
where(can_merge: [true, false])
with_them do
before do
allow(merge_request).to receive(:can_be_merged_by?).and_return(can_merge)
end
it { is_expected.to include({ can_merge: can_merge })}
end
end
end
describe '#issuable_squash_option?' do
using RSpec::Parameterized::TableSyntax

View File

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

View File

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

View File

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

View File

@ -226,7 +226,7 @@ RSpec.shared_examples 'wiki controller actions' do
where(:file_name) { ['dk.png', 'unsanitized.svg', 'git-cheat-sheet.pdf'] }
with_them do
let(:id) { upload_file_to_wiki(container, user, file_name) }
let(:id) { upload_file_to_wiki(wiki, user, file_name) }
it 'delivers the file with the correct headers' do
subject

View File

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

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
require 'spec_helper'
RSpec.describe 'User views AsciiDoc page with includes', :js do
let_it_be(:user) { create(:user) }
RSpec.shared_examples 'User views AsciiDoc page with includes' do
let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' }
let(:project) { create(:project, :public, :wiki_repo) }
let!(:included_wiki_page) { create_wiki_page('included_page', content: 'Content from the included page')}
let!(:wiki_page) { create_wiki_page('home', content: "Content from the main page.\ninclude::included_page.asciidoc[]") }
@ -16,16 +12,16 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
format: :asciidoc
}
create(:wiki_page, wiki: project.wiki, **attrs)
create(:wiki_page, wiki: wiki, **attrs)
end
before do
sign_in(user)
end
context 'when the file being included exists' do
context 'when the file being included exists', :js do
it 'includes the file contents' do
visit(project_wiki_path(project, wiki_page))
visit(wiki_page_path(wiki, wiki_page))
page.within(:css, wiki_content_selector) do
expect(page).to have_content('Content from the main page. Content from the included page')
@ -34,8 +30,10 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
context 'when there are multiple versions of the wiki pages' do
before do
# rubocop:disable Rails/SaveBang
included_wiki_page.update(message: 'updated included file', content: 'Updated content from the included page')
wiki_page.update(message: 'updated wiki page', content: "Updated content from the main page.\ninclude::included_page.asciidoc[]")
# rubocop:enable Rails/SaveBang
end
let(:latest_version_id) { wiki_page.versions.first.id }
@ -43,7 +41,7 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
context 'viewing the latest version' do
it 'includes the latest content' do
visit(project_wiki_path(project, wiki_page, version_id: latest_version_id))
visit(wiki_page_path(wiki, wiki_page, version_id: latest_version_id))
page.within(:css, wiki_content_selector) do
expect(page).to have_content('Updated content from the main page. Updated content from the included page')
@ -53,7 +51,7 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
context 'viewing the original version' do
it 'includes the content from the original version' do
visit(project_wiki_path(project, wiki_page, version_id: oldest_version_id))
visit(wiki_page_path(wiki, wiki_page, version_id: oldest_version_id))
page.within(:css, wiki_content_selector) do
expect(page).to have_content('Content from the main page. Content from the included page')
@ -63,13 +61,13 @@ RSpec.describe 'User views AsciiDoc page with includes', :js do
end
end
context 'when the file being included does not exist' do
context 'when the file being included does not exist', :js do
before do
included_wiki_page.delete
end
it 'outputs an error' do
visit(project_wiki_path(project, wiki_page))
visit(wiki_page_path(wiki, wiki_page))
page.within(:css, wiki_content_selector) do
expect(page).to have_content('Content from the main page. [ERROR: include::included_page.asciidoc[] - unresolved directive]')

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
require 'spec_helper'
# Requires a context containing:
# wiki
# user
RSpec.describe 'User views a wiki page' do
RSpec.shared_examples 'User views a wiki page' do
include WikiHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:path) { 'image.png' }
let(:wiki) { project.wiki }
let(:wiki_page) do
create(:wiki_page,
wiki: wiki,
@ -16,13 +15,12 @@ RSpec.describe 'User views a wiki page' do
end
before do
project.add_maintainer(user)
sign_in(user)
end
context 'when wiki is empty', :js do
before do
visit project_wikis_path(project)
visit wiki_path(wiki)
wait_for_svg_to_be_loaded
@ -83,7 +81,7 @@ RSpec.describe 'User views a wiki page' do
context 'when a page does not have history' do
before do
visit(project_wiki_path(project, wiki_page))
visit(wiki_page_path(wiki, wiki_page))
end
it 'shows all the pages' do
@ -92,7 +90,7 @@ RSpec.describe 'User views a wiki page' do
end
context 'shows a file stored in a page' do
let(:path) { upload_file_to_wiki(project, user, 'dk.png') }
let(:path) { upload_file_to_wiki(wiki, user, 'dk.png') }
it do
expect(page).to have_xpath("//img[@data-src='#{wiki.wiki_base_path}/#{path}']")
@ -121,7 +119,7 @@ RSpec.describe 'User views a wiki page' do
end
it 'shows the page history' do
visit(project_wiki_path(project, wiki_page))
visit(wiki_page_path(wiki, wiki_page))
expect(page).to have_selector('a.btn', text: 'Edit')
@ -133,12 +131,16 @@ RSpec.describe 'User views a wiki page' do
end
it 'does not show the "Edit" button' do
visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id))
visit(wiki_page_path(wiki, wiki_page, version_id: wiki_page.versions.last.id))
expect(page).not_to have_selector('a.btn', text: 'Edit')
end
context 'show the diff' do
before do
skip('Diffing for group wikis will be implemented in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42610') if wiki.container.is_a?(Group)
end
def expect_diff_links(commit)
diff_path = wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
@ -150,7 +152,7 @@ RSpec.describe 'User views a wiki page' do
end
it 'links to the correct diffs' do
visit project_wiki_history_path(project, wiki_page)
visit wiki_page_path(wiki, wiki_page, action: :history)
commit1 = wiki.commit('HEAD^')
commit2 = wiki.commit
@ -208,7 +210,7 @@ RSpec.describe 'User views a wiki page' do
end
it 'preserves the special characters' do
visit(project_wiki_path(project, wiki_page))
visit(wiki_page_path(wiki, wiki_page))
expect(page).to have_css('.wiki-page-title', text: title)
expect(page).to have_css('.wiki-pages li', text: title)
@ -223,7 +225,7 @@ RSpec.describe 'User views a wiki page' do
end
it 'safely displays the page' do
visit(project_wiki_path(project, wiki_page))
visit(wiki_page_path(wiki, wiki_page))
expect(page).to have_css('.wiki-page-title', text: title)
expect(page).to have_content('foo bar')
@ -236,7 +238,7 @@ RSpec.describe 'User views a wiki page' do
end
it 'safely displays the message' do
visit(project_wiki_history_path(project, wiki_page))
visit(wiki_page_path(wiki, wiki_page, action: :history))
expect(page).to have_content('<script>alert(true)<script>')
end
@ -248,7 +250,7 @@ RSpec.describe 'User views a wiki page' do
before do
allow(Gitlab::EncodingHelper).to receive(:encode!).and_return(content)
visit(project_wiki_path(project, wiki_page))
visit(wiki_page_path(wiki, wiki_page))
end
it 'does not show "Edit" button' do
@ -263,7 +265,7 @@ RSpec.describe 'User views a wiki page' do
end
it 'opens a default wiki page', :js do
visit project_path(project)
visit wiki.container.web_url
find('.shortcuts-wiki').click

View File

@ -1,23 +1,22 @@
# frozen_string_literal: true
require 'spec_helper'
# Requires a context containing:
# wiki
# user
RSpec.describe 'User views wiki pages' do
RSpec.shared_examples 'User views wiki pages' do
include WikiHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let!(:wiki_page1) do
create(:wiki_page, wiki: project.wiki, title: '3 home', content: '3')
create(:wiki_page, wiki: wiki, title: '3 home', content: '3')
end
let!(:wiki_page2) do
create(:wiki_page, wiki: project.wiki, title: '1 home', content: '1')
create(:wiki_page, wiki: wiki, title: '1 home', content: '1')
end
let!(:wiki_page3) do
create(:wiki_page, wiki: project.wiki, title: '2 home', content: '2')
create(:wiki_page, wiki: wiki, title: '2 home', content: '2')
end
let(:pages) do
@ -25,9 +24,8 @@ RSpec.describe 'User views wiki pages' do
end
before do
project.add_maintainer(user)
sign_in(user)
visit(project_wikis_pages_path(project))
visit(wiki_path(wiki, action: :pages))
end
context 'ordered by title' do

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