Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-10 21:16:38 +00:00
parent 20c68317f8
commit fbb529e46c
37 changed files with 1641 additions and 2014 deletions

View File

@ -5,5 +5,4 @@ Gitlab/DelegatePredicateMethods:
- app/models/concerns/integrations/base_data_fields.rb
- app/models/project.rb
- ee/app/models/concerns/ee/ci/metadatable.rb
- ee/app/models/license.rb
- lib/gitlab/ci/trace/stream.rb

View File

@ -149,7 +149,7 @@ export default class SourceEditor {
});
this.instances.push(instance);
el.dispatchEvent(new CustomEvent(EDITOR_READY_EVENT, { instance }));
el.dispatchEvent(new CustomEvent(EDITOR_READY_EVENT, { detail: { instance } }));
return instance;
}

View File

@ -1,6 +1,6 @@
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
const defaultTimezone = { name: 'UTC', offset: 0 };
const defaultTimezone = { identifier: 'Etc/UTC', name: 'UTC', offset: 0 };
const defaults = {
$inputEl: null,
$dropdownEl: null,
@ -70,7 +70,7 @@ export default class TimezoneDropdown {
setDropdownValue(timezone) {
this.$dropdownToggle.text(this.displayFormat(timezone));
this.$input.val(timezone.name);
this.$input.val(timezone.identifier);
}
handleDropdownChange({ selectedObj, e }) {

View File

@ -1,5 +1,5 @@
<script>
import { GlButtonGroup, GlButton, GlModalDirective } from '@gitlab/ui';
import { GlButtonGroup, GlButton } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { sprintf, __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -20,9 +20,6 @@ export default {
DeleteBlobModal,
LockButton: () => import('ee_component/repository/components/lock_button.vue'),
},
directives: {
GlModal: GlModalDirective,
},
mixins: [getRefMixin, glFeatureFlagMixin()],
inject: {
targetBranch: {
@ -73,6 +70,10 @@ export default {
type: Boolean,
required: true,
},
showForkSuggestion: {
type: Boolean,
required: true,
},
},
computed: {
replaceModalId() {
@ -91,6 +92,16 @@ export default {
return this.canLock ? 'lock_button' : 'disabled_lock_button';
},
},
methods: {
showModal(modalId) {
if (this.showForkSuggestion) {
this.$emit('fork');
return;
}
this.$refs[modalId].show();
},
},
};
</script>
@ -107,14 +118,15 @@ export default {
data-testid="lock"
:data-qa-selector="lockBtnQASelector"
/>
<gl-button v-gl-modal="replaceModalId" data-testid="replace">
<gl-button data-testid="replace" @click="showModal(replaceModalId)">
{{ $options.i18n.replace }}
</gl-button>
<gl-button v-gl-modal="deleteModalId" data-testid="delete">
<gl-button data-testid="delete" @click="showModal(deleteModalId)">
{{ $options.i18n.delete }}
</gl-button>
</gl-button-group>
<upload-blob-modal
:ref="replaceModalId"
:modal-id="replaceModalId"
:modal-title="replaceModalTitle"
:commit-message="replaceModalTitle"
@ -126,6 +138,7 @@ export default {
:primary-btn-text="$options.i18n.replacePrimaryBtnText"
/>
<delete-blob-modal
:ref="deleteModalId"
:modal-id="deleteModalId"
:modal-title="deleteModalTitle"
:delete-path="deletePath"

View File

@ -280,6 +280,8 @@ export default {
:project-path="projectPath"
:is-locked="Boolean(pathLockedByUser)"
:can-lock="canLock"
:show-fork-suggestion="showForkSuggestion"
@fork="setForkTarget('ide')"
/>
</template>
</blob-header>

View File

@ -146,6 +146,9 @@ export default {
/* eslint-enable dot-notation */
},
methods: {
show() {
this.$refs[this.modalId].show();
},
submitForm(e) {
e.preventDefault(); // Prevent modal from closing
this.form.showValidation = true;
@ -164,6 +167,7 @@ export default {
<template>
<gl-modal
:ref="modalId"
v-bind="$attrs"
data-testid="modal-delete"
:modal-id="modalId"

View File

@ -32,6 +32,7 @@ export default {
class="gl-mr-3"
category="secondary"
variant="confirm"
data-method="post"
:href="forkPath"
data-testid="fork"
>

View File

@ -136,6 +136,9 @@ export default {
},
},
methods: {
show() {
this.$refs[this.modalId].show();
},
setFile(file) {
this.file = file;
@ -206,6 +209,7 @@ export default {
<template>
<gl-form>
<gl-modal
:ref="modalId"
:modal-id="modalId"
:title="modalTitle"
:action-primary="primaryOptions"

View File

@ -87,13 +87,7 @@ class GroupDescendantsFinder
visible_to_user = visible_to_user.or(authorized_to_user)
end
group_to_query = if Feature.enabled?(:linear_group_descendants_finder, current_user, default_enabled: :yaml)
parent_group
else
hierarchy_for_parent
end
group_to_query.descendants.where(visible_to_user)
parent_group.descendants.where(visible_to_user)
# rubocop: enable CodeReuse/Finder
end
# rubocop: enable CodeReuse/ActiveRecord
@ -159,13 +153,7 @@ class GroupDescendantsFinder
# rubocop: disable CodeReuse/ActiveRecord
def projects_matching_filter
# rubocop: disable CodeReuse/Finder
objects_in_hierarchy = if Feature.enabled?(:linear_group_descendants_finder, current_user, default_enabled: :yaml)
parent_group.self_and_descendants.as_ids
else
hierarchy_for_parent.base_and_descendants.select(:id)
end
projects_nested_in_group = Project.where(namespace_id: objects_in_hierarchy)
projects_nested_in_group = Project.where(namespace_id: parent_group.self_and_descendants.as_ids)
params_with_search = params.merge(search: params[:filter])
ProjectsFinder.new(params: params_with_search,

View File

@ -1,8 +0,0 @@
---
name: linear_group_descendants_finder
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68954
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339440
milestone: '14.6'
type: development
group: group::access
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: use_cmark_renderer
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61792
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345744
milestone: '14.6'
type: development
group: group::project management
default_enabled: true

View File

@ -17,16 +17,10 @@ requests, and:
- Take action on individual merge requests.
- Reduce overall cycle time.
NOTE:
Initially, no data appears. Data is populated as users comment on open merge requests.
## Overview
Code Review Analytics is available to users with at least the Reporter [role](../permissions.md), and displays a table of open merge requests that have at least one non-author comment. The review time is measured from the time the first non-author comment was submitted.
To access Code Review Analytics, from your project's menu, go to **Analytics > Code Review**.
You can filter the list of merge requests by milestone and label.
NOTE:
Initially, no data appears. Data is populated as users comment on open merge requests.
![Code Review Analytics](img/code_review_analytics_v13_11.png "List of code reviews; oldest review first.")
@ -36,6 +30,14 @@ The table is sorted by:
or to be broken down into smaller parts.
- Other columns: Display the author, approvers, comment count, and line change (-/+) counts.
## View Code Review Analytics
To view Code Review Analytics:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Analytics > Code Review**.
1. Filter merge requests by milestone and label.
## Use cases
This feature is designed for [development team leaders](https://about.gitlab.com/handbook/marketing/strategic-marketing/roles-personas/#delaney-development-team-lead)

View File

@ -87,7 +87,8 @@ Commit message templates support these variables:
| `%{merged_by}` | User who merged the merge request. | `Alex Garcia <agarcia@example.com>` |
| `%{co_authored_by}` | Names and emails of commit authors in a `Co-authored-by` Git commit trailer format. Limited to authors of 100 most recent commits in merge request. | `Co-authored-by: Zane Doe <zdoe@example.com>` <br> `Co-authored-by: Blake Smith <bsmith@example.com>` |
Empty variables that are the only word in a line are removed, along with all newline characters preceding it.
Any line containing only an empty variable is removed. If the line to be removed is both
preceded and followed by an empty line, the preceding empty line is also removed.
## Related topics

View File

@ -7,13 +7,14 @@ module Banzai
# Footnotes are supported in CommonMark. However we were stripping
# the ids during sanitization. Those are now allowed.
#
# Footnotes are numbered the same - the first one has `id=fn1`, the
# second is `id=fn2`, etc. In order to allow footnotes when rendering
# multiple markdown blocks on a page, we need to make each footnote
# reference unique.
#
# Footnotes are numbered as an increasing integer starting at `1`.
# The `id` associated with a footnote is based on the footnote reference
# string. For example, `[^foot]` will generate `id="fn-foot"`.
# In order to allow footnotes when rendering multiple markdown blocks
# on a page, we need to make each footnote reference unique.
# This filter adds a random number to each footnote (the same number
# can be used for a single render). So you get `id=fn1-4335` and `id=fn2-4335`.
# can be used for a single render). So you get `id=fn-1-4335` and `id=fn-foot-4335`.
#
class FootnoteFilter < HTML::Pipeline::Filter
FOOTNOTE_ID_PREFIX = 'fn-'
@ -26,53 +27,24 @@ module Banzai
CSS_FOOTNOTE = 'sup > a[data-footnote-ref]'
XPATH_FOOTNOTE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_FOOTNOTE).freeze
# only needed when feature flag use_cmark_renderer is turned off
INTEGER_PATTERN = /\A\d+\z/.freeze
FOOTNOTE_ID_PREFIX_OLD = 'fn'
FOOTNOTE_LINK_ID_PREFIX_OLD = 'fnref'
FOOTNOTE_LI_REFERENCE_PATTERN_OLD = /\A#{FOOTNOTE_ID_PREFIX_OLD}\d+\z/.freeze
FOOTNOTE_LINK_REFERENCE_PATTERN_OLD = /\A#{FOOTNOTE_LINK_ID_PREFIX_OLD}\d+\z/.freeze
FOOTNOTE_START_NUMBER = 1
CSS_SECTION_OLD = "ol > li[id=#{FOOTNOTE_ID_PREFIX_OLD}#{FOOTNOTE_START_NUMBER}]"
XPATH_SECTION_OLD = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION_OLD).freeze
def call
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
# Sanitization stripped off the section class - add it back in
return doc unless section_node = doc.at_xpath(XPATH_SECTION)
# Sanitization stripped off the section class - add it back in
return doc unless section_node = doc.at_xpath(XPATH_SECTION)
section_node.append_class('footnotes')
else
return doc unless first_footnote = doc.at_xpath(XPATH_SECTION_OLD)
return doc unless first_footnote.parent
first_footnote.parent.wrap('<section class="footnotes">')
end
section_node.append_class('footnotes')
rand_suffix = "-#{random_number}"
modified_footnotes = {}
xpath_footnote = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
XPATH_FOOTNOTE
else
Gitlab::Utils::Nokogiri.css_to_xpath('sup > a[id]')
end
doc.xpath(XPATH_FOOTNOTE).each do |link_node|
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
ref_num.gsub!(/[[:punct:]]/, '\\\\\&')
doc.xpath(xpath_footnote).each do |link_node|
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
ref_num.gsub!(/[[:punct:]]/, '\\\\\&')
else
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX_OLD)
end
css = Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? "section[data-footnotes] li[id=#{fn_id(ref_num)}]" : "li[id=#{fn_id(ref_num)}]"
css = "section[data-footnotes] li[id=#{fn_id(ref_num)}]"
node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath(css)
footnote_node = doc.at_xpath(node_xpath)
if footnote_node || modified_footnotes[ref_num]
next if Feature.disabled?(:use_cmark_renderer, default_enabled: :yaml) && !INTEGER_PATTERN.match?(ref_num)
link_node[:href] += rand_suffix
link_node[:id] += rand_suffix
@ -103,13 +75,11 @@ module Banzai
end
def fn_id(num)
prefix = Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? FOOTNOTE_ID_PREFIX : FOOTNOTE_ID_PREFIX_OLD
"#{prefix}#{num}"
"#{FOOTNOTE_ID_PREFIX}#{num}"
end
def fnref_id(num)
prefix = Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? FOOTNOTE_LINK_ID_PREFIX : FOOTNOTE_LINK_ID_PREFIX_OLD
"#{prefix}#{num}"
"#{FOOTNOTE_LINK_ID_PREFIX}#{num}"
end
end
end

View File

@ -4,8 +4,8 @@
# This module is used in Banzai::Filter::MarkdownFilter.
# Used gem is `commonmarker` which is a ruby wrapper for libcmark (CommonMark parser)
# including GitHub's GFM extensions.
# We now utilize the renderer built in `C`, rather than the ruby based renderer.
# Homepage: https://github.com/gjtorikian/commonmarker
module Banzai
module Filter
module MarkdownEngines
@ -22,57 +22,29 @@ module Banzai
:VALIDATE_UTF8 # replace illegal sequences with the replacement character U+FFFD.
].freeze
RENDER_OPTIONS_C = [
RENDER_OPTIONS = [
:GITHUB_PRE_LANG, # use GitHub-style <pre lang> for fenced code blocks.
:FOOTNOTES, # render footnotes.
:FULL_INFO_STRING, # include full info strings of code blocks in separate attribute.
:UNSAFE # allow raw/custom HTML and unsafe links.
].freeze
# The `:GITHUB_PRE_LANG` option is not used intentionally because
# it renders a fence block with language as `<pre lang="LANG"><code>some code\n</code></pre>`
# while GitLab's syntax is `<pre><code lang="LANG">some code\n</code></pre>`.
# If in the future the syntax is about to be made GitHub-compatible, please, add `:GITHUB_PRE_LANG` render option below
# and remove `code_block` method from `lib/banzai/renderer/common_mark/html.rb`.
RENDER_OPTIONS_RUBY = [
# as of commonmarker 0.18.0, we need to use :UNSAFE to get the same as the original :DEFAULT
# https://github.com/gjtorikian/commonmarker/pull/81
:UNSAFE # allow raw/custom HTML and unsafe links.
].freeze
def initialize(context)
@context = context
@renderer = Banzai::Renderer::CommonMark::HTML.new(options: render_options) if Feature.disabled?(:use_cmark_renderer, default_enabled: :yaml)
end
def render(text)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
CommonMarker.render_html(text, render_options, extensions)
else
doc = CommonMarker.render_doc(text, PARSE_OPTIONS, extensions)
@renderer.render(doc)
end
CommonMarker.render_html(text, render_options, EXTENSIONS)
end
private
def extensions
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
EXTENSIONS
else
EXTENSIONS + [
:tagfilter # strips out several "unsafe" HTML tags from being used: https://github.github.com/gfm/#disallowed-raw-html-extension-
].freeze
end
end
def render_options
@context[:no_sourcepos] ? render_options_no_sourcepos : render_options_sourcepos
end
def render_options_no_sourcepos
Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? RENDER_OPTIONS_C : RENDER_OPTIONS_RUBY
RENDER_OPTIONS
end
def render_options_sourcepos

View File

@ -8,8 +8,10 @@ module Banzai
NOT_LITERAL_REGEX = %r{#{LITERAL_KEYWORD}-((%5C|\\).+?)-#{LITERAL_KEYWORD}}.freeze
SPAN_REGEX = %r{<span>(.*?)</span>}.freeze
CSS_A = 'a'
XPATH_A = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_A).freeze
CSS_A = 'a'
XPATH_A = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_A).freeze
CSS_LANG_TAG = 'pre'
XPATH_LANG_TAG = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_LANG_TAG).freeze
def call
return doc unless result[:escaped_literals]
@ -32,22 +34,12 @@ module Banzai
node.attributes['title'].value = node.attributes['title'].value.gsub(SPAN_REGEX, '\1') if node.attributes['title']
end
doc.xpath(lang_tag).each do |node|
doc.xpath(XPATH_LANG_TAG).each do |node|
node.attributes['lang'].value = node.attributes['lang'].value.gsub(SPAN_REGEX, '\1') if node.attributes['lang']
end
doc
end
private
def lang_tag
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
Gitlab::Utils::Nokogiri.css_to_xpath('pre')
else
Gitlab::Utils::Nokogiri.css_to_xpath('code')
end
end
end
end
end

View File

@ -25,12 +25,7 @@ module Banzai
private
def lang_tag
@lang_tag ||=
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
Gitlab::Utils::Nokogiri.css_to_xpath('pre[lang="plantuml"] > code').freeze
else
Gitlab::Utils::Nokogiri.css_to_xpath('pre > code[lang="plantuml"]').freeze
end
@lang_tag ||= Gitlab::Utils::Nokogiri.css_to_xpath('pre[lang="plantuml"] > code').freeze
end
def settings

View File

@ -28,12 +28,10 @@ module Banzai
allowlist[:attributes]['li'] = %w[id]
allowlist[:transformers].push(self.class.remove_non_footnote_ids)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
# Allow section elements with data-footnotes attribute
allowlist[:elements].push('section')
allowlist[:attributes]['section'] = %w(data-footnotes)
allowlist[:attributes]['a'].push('data-footnote-ref', 'data-footnote-backref')
end
# Allow section elements with data-footnotes attribute
allowlist[:elements].push('section')
allowlist[:attributes]['section'] = %w(data-footnotes)
allowlist[:attributes]['a'].push('data-footnote-ref', 'data-footnote-backref')
allowlist
end
@ -61,13 +59,8 @@ module Banzai
return unless node.name == 'a' || node.name == 'li'
return unless node.has_attribute?('id')
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN
return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN
else
return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN_OLD
return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN_OLD
end
return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN
return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN
node.remove_attribute('id')
end

View File

@ -70,11 +70,11 @@ module Banzai
private
def parse_lang_params(node)
node = node.parent if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
node = node.parent
# Commonmarker's FULL_INFO_STRING render option works with the space delimiter.
# But the current behavior of GitLab's markdown renderer is different - it grabs everything as the single
# line, including language and its options. To keep backward compatability, we have to parse the old format and
# line, including language and its options. To keep backward compatibility, we have to parse the old format and
# merge with the new one.
#
# Behaviors before separating language and its parameters:
@ -91,11 +91,7 @@ module Banzai
return unless language
language, language_params = language.split(LANG_PARAMS_DELIMITER, 2)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
language_params = [node.attr('data-meta'), language_params].compact.join(' ')
end
language_params = [node.attr('data-meta'), language_params].compact.join(' ')
formatted_language_params = format_language_params(language_params)
[language, formatted_language_params]

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
# Remove this entire file when removing `use_cmark_renderer` feature flag and switching to the CMARK html renderer.
# https://gitlab.com/gitlab-org/gitlab/-/issues/345744
module Banzai
module Renderer
module CommonMark
class HTML < CommonMarker::HtmlRenderer
def code_block(node)
block do
out("<pre#{sourcepos(node)}><code")
out(' lang="', node.fence_info, '"') if node.fence_info.present?
out('>')
out(escape_html(node.string_content))
out('</code></pre>')
end
end
end
end
end
end

View File

@ -7,11 +7,7 @@ module Gitlab
register_for 'gitlab-html-pipeline'
def format(node, lang, opts)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
%(<pre #{lang ? %[lang="#{lang}"] : ''}><code>#{node.content}</code></pre>)
else
%(<pre><code #{lang ? %[ lang="#{lang}"] : ''}>#{node.content}</code></pre>)
end
%(<pre #{lang ? %[lang="#{lang}"] : ''}><code>#{node.content}</code></pre>)
end
end
end

View File

@ -65,9 +65,13 @@ module Gitlab
end
names_of_empty_variables = values.filter_map { |name, value| name if value.blank? }
# Remove placeholders that correspond to empty values and are the only word in a line
# along with all whitespace characters preceding them.
message = message.gsub(/[\n\r]+#{Regexp.union(names_of_empty_variables)}$/, '') if names_of_empty_variables.present?
# Remove lines that contain empty variable placeholder and nothing else.
if names_of_empty_variables.present?
# If there is blank line or EOF after it, remove blank line before it as well.
message = message.gsub(/\n\n#{Regexp.union(names_of_empty_variables)}(\n\n|\Z)/, '\1')
# Otherwise, remove only the line it is in.
message = message.gsub(/^#{Regexp.union(names_of_empty_variables)}\n/, '')
end
# Substitute all variables with their values.
message = message.gsub(Regexp.union(values.keys), values) if values.present?

View File

@ -1,209 +1,199 @@
{
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 4.835599899291992,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 26.39240026473999,
"qa/specs/features/ee/browser_ui/secure/create_project_with_secure_spec.rb": 46.76790499687195,
"qa/specs/features/api/1_manage/users_spec.rb": 0.6089541912078857,
"qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb": 20.58603596687317,
"qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb": 31.49540114402771,
"qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb": 16.18057894706726,
"qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb": 18.047621726989746,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 19.459370374679565,
"qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb": 22.800872802734375,
"qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb": 5.812374591827393,
"qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb": 17.273863554000854,
"qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb": 8.31815505027771,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 51.81568956375122,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 30.373554468154907,
"qa/specs/features/sanity/version_spec.rb": 0.0004665851593017578,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 22.993898630142212,
"qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb": 36.358747243881226,
"qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb": 9.079580068588257,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 39.412545919418335,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 27.905837297439575,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 99.71209812164307,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 25.72262978553772,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 44.536749839782715,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 18.482256174087524,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 16.314701795578003,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 36.934654235839844,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 17.103047847747803,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 28.352823495864868,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 20.208287954330444,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 13.733941793441772,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 16.376311540603638,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 98.71593880653381,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 21.307689666748047,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 39.39231467247009,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 18.50350284576416,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 30.67392325401306,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 16.17888569831848,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 45.47245216369629,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 38.93913507461548,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 12.087258100509644,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 17.309232473373413,
"qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb": 70.75156497955322,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 22.20275855064392,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 24.21539831161499,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 20.22646951675415,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 31.00749373435974,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 18.430193424224854,
"qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb": 13.615312337875366,
"qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb": 94.04865074157715,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 59.575963258743286,
"qa/specs/features/ee/browser_ui/1_manage/group/share_group_with_group_spec.rb": 25.6483793258667,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 26.07162046432495,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 8.41316819190979,
"qa/specs/features/ee/browser_ui/secure/merge_request_license_widget_spec.rb": 73.56247329711914,
"qa/specs/features/api/3_create/repository/files_spec.rb": 6.235261917114258,
"qa/specs/features/browser_ui/5_package/container_registry_spec.rb": 7.263134717941284,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 10.079012870788574,
"qa/specs/features/browser_ui/3_create/repository/file/delete_file_via_web_spec.rb": 16.52791404724121,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 10.684799909591675,
"qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 72.14465713500977,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 15.664107322692871,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 34.32576060295105,
"qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb": 31.586787939071655,
"qa/specs/features/ee/browser_ui/secure/project_security_dashboard_spec.rb": 27.6742160320282,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 11.836639165878296,
"qa/specs/features/ee/browser_ui/1_manage/user/minimal_access_user_spec.rb": 15.691015005111694,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 38.80854368209839,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 15.07186245918274,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 41.58929800987244,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 13.438591957092285,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 30.66688632965088,
"qa/specs/features/browser_ui/1_manage/user/user_access_termination_spec.rb": 25.137597799301147,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 8.731743574142456,
"qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb": 0.0008337497711181641,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 40.60329842567444,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 39.090237617492676,
"qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb": 22.633376359939575,
"qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb": 18.457061767578125,
"qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb": 49.273433685302734,
"qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb": 14.6923348903656,
"qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 38.44519090652466,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 46.04780888557434,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb": 27.77385425567627,
"qa/specs/features/ee/browser_ui/1_manage/insights/default_insights_spec.rb": 28.850987195968628,
"qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb": 19.598198413848877,
"qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb": 15.17819595336914,
"qa/specs/features/api/1_manage/user_access_termination_spec.rb": 6.3396782875061035,
"qa/specs/features/ee/browser_ui/secure/create_merge_request_with_secure_spec.rb": 71.55253982543945,
"qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb": 42.83795666694641,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 17.03721809387207,
"qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb": 25.14606213569641,
"qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb": 70.44876503944397,
"qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb": 52.93111038208008,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 48.3312201499939,
"qa/specs/features/ee/browser_ui/1_manage/group/group_audit_logs_2_spec.rb": 100.1787781715393,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 22.37237572669983,
"qa/specs/features/ee/browser_ui/1_manage/group/group_file_template_spec.rb": 69.36870694160461,
"qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb": 13.524356126785278,
"qa/specs/features/ee/browser_ui/1_manage/group/prevent_forking_outside_group_spec.rb": 37.17233395576477,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 6.892294406890869,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 12.628767013549805,
"qa/specs/features/ee/browser_ui/secure/enable_sast_from_configuration_spec.rb": 86.38991785049438,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_for_project_mirror_github_spec.rb": 12.684113502502441,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 35.340261459350586,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 19.719850540161133,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 38.779794216156006,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 6.86094856262207,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 51.54451298713684,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 18.586050033569336,
"qa/specs/features/browser_ui/3_create/web_ide/open_web_ide_from_diff_tab_spec.rb": 35.587294578552246,
"qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb": 14.01547122001648,
"qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 20.370421409606934,
"qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb": 15.297787427902222,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 42.507469177246094,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_status_on_operation_dashboard_spec.rb": 46.77937316894531,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb": 20.96509575843811,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 12.62861156463623,
"qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 49.85329723358154,
"qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb": 84.63548684120178,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 15.584527254104614,
"qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb": 30.72457480430603,
"qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb": 10.70707631111145,
"qa/specs/features/ee/api/1_manage/user/minimal_access_user_spec.rb": 4.82462477684021,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 32.49313712120056,
"qa/specs/features/browser_ui/3_create/wiki/project_based_list_spec.rb": 32.37481236457825,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 16.573434114456177,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 43.15674066543579,
"qa/specs/features/ee/browser_ui/secure/license_compliance_spec.rb": 31.000027179718018,
"qa/specs/features/ee/browser_ui/1_manage/project/project_audit_logs_spec.rb": 149.64519357681274,
"qa/specs/features/ee/browser_ui/1_manage/group/restrict_by_ip_address_spec.rb": 116.07316851615906,
"qa/specs/features/browser_ui/1_manage/login/register_spec.rb": 145.5431580543518,
"qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb": 19.418848514556885,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 18.54582905769348,
"qa/specs/features/api/5_package/container_registry_spec.rb": 3.93778920173645,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 148.70570707321167,
"qa/specs/features/ee/browser_ui/6_release/multi-project_pipelines_spec.rb": 52.770936250686646,
"qa/specs/features/ee/browser_ui/3_create/contribution_analytics_spec.rb": 45.81806302070618,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 20.386794805526733,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb": 21.968912363052368,
"qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb": 71.7951602935791,
"qa/specs/features/api/1_manage/bulk_import_group_spec.rb": 50.21021842956543,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 16.42144775390625,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 173.84056043624878,
"qa/specs/features/ee/browser_ui/1_manage/project/project_templates_spec.rb": 86.48439168930054,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 56.275856018066406,
"qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 87.7243127822876,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 98.70601177215576,
"qa/specs/features/ee/browser_ui/secure/vulnerability_management_spec.rb": 91.42165040969849,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 176.7654173374176,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 17.273313283920288,
"qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb": 15.879833459854126,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 16.24162793159485,
"qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb": 55.61779570579529,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 15.482072830200195,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 54.22519111633301,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 85.68487024307251,
"qa/specs/features/ee/browser_ui/3_create/web_ide/web_terminal_spec.rb": 63.027522802352905,
"qa/specs/features/ee/browser_ui/3_create/wiki/create_group_wiki_page_spec.rb": 26.17377734184265,
"qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb": 16.853343725204468,
"qa/specs/features/api/1_manage/project_access_token_spec.rb": 2.8215739727020264,
"qa/specs/features/ee/browser_ui/1_manage/instance/instance_audit_logs_spec.rb": 109.0079870223999,
"qa/specs/features/browser_ui/6_release/pages/pages_pipeline_spec.rb": 73.78986692428589,
"qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb": 34.84124970436096,
"qa/specs/features/api/1_manage/import_github_repo_spec.rb": 11.330891609191895,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 36.70389533042908,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 25.407151699066162,
"qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb": 109.88336181640625,
"qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb": 24.014917373657227,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 42.596492767333984,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 60.067442893981934,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 185.41551113128662,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 30.03315234184265,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 76.58771228790283,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 28.64104723930359,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 198.37600350379944,
"qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb": 35.098846435546875,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 51.25122785568237,
"qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb": 61.072656869888306,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 135.90494751930237,
"qa/specs/features/ee/browser_ui/4_verify/cancelling_merge_request_in_merge_train_spec.rb": 59.57308530807495,
"qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb": 32.462252140045166,
"qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb": 53.246745586395264,
"qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb": 79.99787259101868,
"qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb": 57.00465273857117,
"qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 77.2134940624237,
"qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb": 48.472514390945435,
"qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb": 17.153425455093384,
"qa/specs/features/ee/browser_ui/1_manage/group/group_audit_logs_1_spec.rb": 96.45902276039124,
"qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb": 64.81094217300415,
"qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb": 27.902702569961548,
"qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb": 23.476325750350952,
"qa/specs/features/ee/browser_ui/10_protect/policy_list_spec.rb": 14.327334642410278,
"qa/specs/features/ee/browser_ui/secure/security_reports_spec.rb": 26.599382877349854,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 39.33750772476196,
"qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb": 50.194467306137085,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 59.395915508270264,
"qa/specs/features/ee/browser_ui/4_verify/pipelines_for_merged_results_and_merge_trains_spec.rb": 203.04975271224976,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 42.03165292739868,
"qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb": 18.495836973190308,
"qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb": 17.1261248588562,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 53.17536449432373,
"qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 62.42040038108826,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 90.16095304489136,
"qa/specs/features/ee/browser_ui/4_verify/new_discussion_not_dropping_merge_trains_mr_spec.rb": 91.91785287857056,
"qa/specs/features/browser_ui/5_package/maven_repository_spec.rb": 687.8482820987701,
"qa/specs/features/browser_ui/5_package/npm_registry_spec.rb": 225.33412504196167
"qa/specs/features/api/1_manage/users_spec.rb": 0.38025754499994946,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 6.1726223529999515,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 6.837727864000044,
"qa/specs/features/api/3_create/repository/files_spec.rb": 6.9398801430002095,
"qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb": 7.072401565999826,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 8.430185218000133,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 9.21629216500014,
"qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb": 9.460245247999865,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 9.749626129999797,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 9.997606156000074,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 10.100938496999788,
"qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb": 10.435720339999989,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 10.664103456999783,
"qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb": 11.010621653000044,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 11.080987325000024,
"qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb": 11.289408692999814,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 11.388780440000119,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 11.812601945000097,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 11.884903447000397,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 11.973681912000075,
"qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb": 11.982320482999967,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 12.454461346999778,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 12.595205497999814,
"qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb": 12.696943737999845,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 12.821059261999835,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 13.493086933000086,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 13.522706569999855,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 13.611114558000281,
"qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb": 13.662936730000183,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 13.997128957999848,
"qa/specs/features/ee/browser_ui/10_protect/policy_alerts_list_spec.rb": 14.055217947000074,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 14.212461153999811,
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 14.218627059000028,
"qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb": 14.295524570999987,
"qa/specs/features/api/1_manage/project_access_token_spec.rb": 14.394589879999785,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 14.505683429000328,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 14.804579386000114,
"qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb": 14.869172961000004,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 15.069100258000162,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 15.071375434999936,
"qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb": 15.238976046000062,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 15.25893454900006,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 15.62486792799973,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 15.73652188899996,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 16.175388279000117,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 16.21438098999988,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 16.22156817799987,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 16.28064358199981,
"qa/specs/features/browser_ui/3_create/repository/file/delete_file_via_web_spec.rb": 16.526415993999763,
"qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb": 16.635663933999695,
"qa/specs/features/api/1_manage/import_github_repo_spec.rb": 16.75859540500005,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 16.85444325000026,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 17.065811306999876,
"qa/specs/features/ee/browser_ui/11_fulfillment/purchase/user_registration_billing_spec.rb": 17.17362991999994,
"qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb": 17.35990919599999,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 17.4036377489997,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 17.53151273000003,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb": 18.336858511999708,
"qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb": 18.827946458000042,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 19.011096268000074,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 19.273840846999974,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 19.779615910000075,
"qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 19.787533178000103,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 20.357167043000118,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 20.58807039300018,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 20.604954283000097,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 20.84536794700034,
"qa/specs/features/ee/browser_ui/3_create/wiki/create_group_wiki_page_spec.rb": 21.285323852000147,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb": 21.324911325999892,
"qa/specs/features/api/1_manage/user_access_termination_spec.rb": 21.359333020000122,
"qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb": 21.53934234799999,
"qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb": 21.623363159999826,
"qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb": 21.639708403999975,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb": 22.508069357000295,
"qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb": 22.67048247999992,
"qa/specs/features/ee/browser_ui/1_manage/insights/default_insights_spec.rb": 22.707359102999817,
"qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb": 23.914098683999782,
"qa/specs/features/ee/browser_ui/1_manage/group/share_group_with_group_spec.rb": 24.228926759999922,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 24.320835930999692,
"qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb": 24.421617836999985,
"qa/specs/features/ee/api/1_manage/user/minimal_access_user_spec.rb": 24.568071621999934,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 25.105601008999656,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 25.670671182000206,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 26.588750723999965,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 26.957921672999873,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 27.038084878000063,
"qa/specs/features/ee/browser_ui/1_manage/user/minimal_access_user_spec.rb": 27.164655879999827,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 27.958475461000035,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 29.15091494699982,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 29.63945763499987,
"qa/specs/features/browser_ui/3_create/wiki/project_based_list_spec.rb": 30.055674564000128,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 30.36701765900034,
"qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb": 31.11626907400023,
"qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb": 31.399775493000107,
"qa/specs/features/ee/browser_ui/10_protect/policies_list_spec.rb": 31.579298365999875,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 31.699020851000114,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 32.22923225600016,
"qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb": 32.390305334999994,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 32.4001524549999,
"qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb": 32.588556773000164,
"qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb": 32.655282309000086,
"qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb": 32.67606646000013,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 32.86781491000011,
"qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb": 33.592668581,
"qa/specs/features/browser_ui/14_non_devops/performance_bar_spec.rb": 33.844586084000184,
"qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb": 34.22076284800005,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 34.69707643500033,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 35.48510294100015,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 35.60547228999985,
"qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb": 35.720513429999755,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 36.74466744499978,
"qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 36.86498900599986,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 36.88383336300012,
"qa/specs/features/browser_ui/3_create/web_ide/open_web_ide_from_diff_tab_spec.rb": 36.91027954099991,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 38.008783447999576,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 38.2163332099999,
"qa/specs/features/browser_ui/1_manage/user/user_access_termination_spec.rb": 38.41997747000005,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 38.798842664000176,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 38.86858395499985,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 39.11634360200014,
"qa/specs/features/ee/browser_ui/1_manage/group/prevent_forking_outside_group_spec.rb": 39.33498874499992,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 39.408525157999975,
"qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb": 39.66159539199998,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_status_on_operation_dashboard_spec.rb": 39.74350435799988,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 39.873689517,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 41.68991280600039,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 42.34842452200019,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 42.87835724299998,
"qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb": 43.00558933000002,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 45.002151569000034,
"qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb": 45.135388796999905,
"qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 45.27653511099993,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 45.49861126799988,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 45.95718983799998,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 46.274925919,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 47.46850344200038,
"qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb": 47.98207172000002,
"qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb": 48.11739431199976,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 48.333632795000085,
"qa/specs/features/ee/browser_ui/6_release/multi-project_pipelines_spec.rb": 49.07156694399987,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 49.98554557300031,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 50.13713323600041,
"qa/specs/features/ee/api/1_manage/bulk_import_group_spec.rb": 50.35492376299999,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 50.79862357000002,
"qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb": 51.18897452500005,
"qa/specs/features/ee/browser_ui/3_create/contribution_analytics_spec.rb": 51.5182317230001,
"qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb": 52.09437633100015,
"qa/specs/features/ee/browser_ui/13_secure/license_compliance_spec.rb": 53.78650449299994,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 53.991341565000084,
"qa/specs/features/ee/browser_ui/13_secure/create_merge_request_with_secure_spec.rb": 56.61833271799969,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 58.94148023099979,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 59.88873274600019,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 61.892666940000254,
"qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb": 64.76709299799995,
"qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 65.38459789100034,
"qa/specs/features/ee/browser_ui/13_secure/merge_request_license_widget_spec.rb": 65.64657268999963,
"qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 66.96891640700005,
"qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb": 68.11150705099999,
"qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb": 70.67798023099999,
"qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb": 71.9568110780001,
"qa/specs/features/browser_ui/6_release/pages/pages_pipeline_spec.rb": 73.23625617499965,
"qa/specs/features/ee/browser_ui/1_manage/group/group_file_template_spec.rb": 74.34492043599994,
"qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb": 75.80247018900013,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 75.8654343979997,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 76.87336582600028,
"qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb": 82.25273063899976,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 82.48331730200016,
"qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb": 84.2234556809999,
"qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 86.64432922500009,
"qa/specs/features/ee/browser_ui/1_manage/project/project_templates_spec.rb": 89.27986745599992,
"qa/specs/features/ee/browser_ui/4_verify/new_discussion_not_dropping_merge_trains_mr_spec.rb": 90.51838889500004,
"qa/specs/features/ee/browser_ui/4_verify/cancelling_merge_request_in_merge_train_spec.rb": 99.40036980900004,
"qa/specs/features/ee/browser_ui/1_manage/group/group_audit_logs_1_spec.rb": 99.88996194999982,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 100.75571312800002,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 102.73961456400002,
"qa/specs/features/ee/browser_ui/4_verify/pipelines_for_merged_results_and_merge_trains_spec.rb": 108.17952484600005,
"qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb": 108.78527251000014,
"qa/specs/features/ee/browser_ui/1_manage/group/restrict_by_ip_address_spec.rb": 108.98986279500014,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 109.23582328299972,
"qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 117.43055826199998,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 120.7805862900002,
"qa/specs/features/ee/browser_ui/13_secure/security_reports_spec.rb": 121.40739863099998,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 121.93682936799996,
"qa/specs/features/ee/browser_ui/1_manage/group/group_audit_logs_2_spec.rb": 124.35619795699995,
"qa/specs/features/ee/browser_ui/1_manage/instance/instance_audit_logs_spec.rb": 129.314630158,
"qa/specs/features/ee/browser_ui/13_secure/vulnerability_management_spec.rb": 132.3623656059999,
"qa/specs/features/ee/browser_ui/13_secure/enable_scanning_from_configuration_spec.rb": 135.19990866599983,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 136.47575045699978,
"qa/specs/features/ee/browser_ui/13_secure/project_security_dashboard_spec.rb": 137.37273152800026,
"qa/specs/features/ee/browser_ui/1_manage/project/project_audit_logs_spec.rb": 154.7787124900001,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 161.17543870600002,
"qa/specs/features/api/1_manage/bulk_import_group_spec.rb": 167.9717091839998,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 192.326786567,
"qa/specs/features/browser_ui/1_manage/login/register_spec.rb": 412.09824056499974,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 428.72626845000013,
"qa/specs/features/api/1_manage/bulk_import_project_spec.rb": 686.55653471,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 923.7095398920001
}

View File

@ -17,262 +17,250 @@ RSpec.describe GroupDescendantsFinder do
described_class.new(current_user: user, parent_group: group, params: params)
end
shared_examples 'group descentants finder examples' do
describe '#has_children?' do
describe '#has_children?' do
it 'is true when there are projects' do
create(:project, namespace: group)
expect(finder.has_children?).to be_truthy
end
context 'when there are subgroups' do
it 'is true when there are projects' do
create(:project, namespace: group)
create(:group, parent: group)
expect(finder.has_children?).to be_truthy
end
end
end
context 'when there are subgroups' do
it 'is true when there are projects' do
create(:group, parent: group)
describe '#execute' do
it 'includes projects' do
project = create(:project, namespace: group)
expect(finder.has_children?).to be_truthy
end
expect(finder.execute).to contain_exactly(project)
end
context 'when archived is `true`' do
let(:params) { { archived: 'true' } }
it 'includes archived projects' do
archived_project = create(:project, namespace: group, archived: true)
project = create(:project, namespace: group)
expect(finder.execute).to contain_exactly(archived_project, project)
end
end
describe '#execute' do
it 'includes projects' do
project = create(:project, namespace: group)
context 'when archived is `only`' do
let(:params) { { archived: 'only' } }
expect(finder.execute).to contain_exactly(project)
it 'includes only archived projects' do
archived_project = create(:project, namespace: group, archived: true)
_project = create(:project, namespace: group)
expect(finder.execute).to contain_exactly(archived_project)
end
end
it 'does not include archived projects' do
_archived_project = create(:project, :archived, namespace: group)
expect(finder.execute).to be_empty
end
context 'with a filter' do
let(:params) { { filter: 'test' } }
it 'includes only projects matching the filter' do
_other_project = create(:project, namespace: group)
matching_project = create(:project, namespace: group, name: 'testproject')
expect(finder.execute).to contain_exactly(matching_project)
end
end
it 'sorts elements by name as default' do
project1 = create(:project, namespace: group, name: 'z')
project2 = create(:project, namespace: group, name: 'a')
expect(subject.execute).to match_array([project2, project1])
end
context 'sorting by name' do
let!(:project1) { create(:project, namespace: group, name: 'a', path: 'project-a') }
let!(:project2) { create(:project, namespace: group, name: 'z', path: 'project-z') }
let(:params) do
{
sort: 'name_asc'
}
end
context 'when archived is `true`' do
let(:params) { { archived: 'true' } }
it 'includes archived projects' do
archived_project = create(:project, namespace: group, archived: true)
project = create(:project, namespace: group)
expect(finder.execute).to contain_exactly(archived_project, project)
end
it 'sorts elements by name' do
expect(subject.execute).to eq(
[
project1,
project2
]
)
end
context 'when archived is `only`' do
let(:params) { { archived: 'only' } }
it 'includes only archived projects' do
archived_project = create(:project, namespace: group, archived: true)
_project = create(:project, namespace: group)
expect(finder.execute).to contain_exactly(archived_project)
end
end
it 'does not include archived projects' do
_archived_project = create(:project, :archived, namespace: group)
expect(finder.execute).to be_empty
end
context 'with a filter' do
let(:params) { { filter: 'test' } }
it 'includes only projects matching the filter' do
_other_project = create(:project, namespace: group)
matching_project = create(:project, namespace: group, name: 'testproject')
expect(finder.execute).to contain_exactly(matching_project)
end
end
it 'sorts elements by name as default' do
project1 = create(:project, namespace: group, name: 'z')
project2 = create(:project, namespace: group, name: 'a')
expect(subject.execute).to match_array([project2, project1])
end
context 'sorting by name' do
let!(:project1) { create(:project, namespace: group, name: 'a', path: 'project-a') }
let!(:project2) { create(:project, namespace: group, name: 'z', path: 'project-z') }
let(:params) do
{
sort: 'name_asc'
}
end
context 'with nested groups' do
let!(:subgroup1) { create(:group, parent: group, name: 'a', path: 'sub-a') }
let!(:subgroup2) { create(:group, parent: group, name: 'z', path: 'sub-z') }
it 'sorts elements by name' do
expect(subject.execute).to eq(
[
subgroup1,
subgroup2,
project1,
project2
]
)
end
context 'with nested groups' do
let!(:subgroup1) { create(:group, parent: group, name: 'a', path: 'sub-a') }
let!(:subgroup2) { create(:group, parent: group, name: 'z', path: 'sub-z') }
it 'sorts elements by name' do
expect(subject.execute).to eq(
[
subgroup1,
subgroup2,
project1,
project2
]
)
end
end
end
it 'does not include projects shared with the group' do
project = create(:project, namespace: group)
other_project = create(:project)
other_project.project_group_links.create!(group: group,
group_access: Gitlab::Access::MAINTAINER)
expect(finder.execute).to contain_exactly(project)
end
end
context 'with shared groups' do
let_it_be(:other_group) { create(:group) }
let_it_be(:shared_group_link) do
create(:group_group_link,
shared_group: group,
shared_with_group: other_group)
end
it 'does not include projects shared with the group' do
project = create(:project, namespace: group)
other_project = create(:project)
other_project.project_group_links.create!(group: group,
group_access: Gitlab::Access::MAINTAINER)
context 'without common ancestor' do
expect(finder.execute).to contain_exactly(project)
end
end
context 'with shared groups' do
let_it_be(:other_group) { create(:group) }
let_it_be(:shared_group_link) do
create(:group_group_link,
shared_group: group,
shared_with_group: other_group)
end
context 'without common ancestor' do
it { expect(finder.execute).to be_empty }
end
context 'with common ancestor' do
let_it_be(:common_ancestor) { create(:group) }
let_it_be(:other_group) { create(:group, parent: common_ancestor) }
let_it_be(:group) { create(:group, parent: common_ancestor) }
context 'querying under the common ancestor' do
it { expect(finder.execute).to be_empty }
end
context 'with common ancestor' do
let_it_be(:common_ancestor) { create(:group) }
let_it_be(:other_group) { create(:group, parent: common_ancestor) }
let_it_be(:group) { create(:group, parent: common_ancestor) }
context 'querying under the common ancestor' do
it { expect(finder.execute).to be_empty }
context 'querying the common ancestor' do
subject(:finder) do
described_class.new(current_user: user, parent_group: common_ancestor, params: params)
end
context 'querying the common ancestor' do
subject(:finder) do
described_class.new(current_user: user, parent_group: common_ancestor, params: params)
end
it 'contains shared subgroups' do
expect(finder.execute).to contain_exactly(group, other_group)
end
it 'contains shared subgroups' do
expect(finder.execute).to contain_exactly(group, other_group)
end
end
end
end
context 'with nested groups' do
let!(:project) { create(:project, namespace: group) }
let!(:subgroup) { create(:group, :private, parent: group) }
context 'with nested groups' do
let!(:project) { create(:project, namespace: group) }
let!(:subgroup) { create(:group, :private, parent: group) }
describe '#execute' do
it 'contains projects and subgroups' do
expect(finder.execute).to contain_exactly(subgroup, project)
describe '#execute' do
it 'contains projects and subgroups' do
expect(finder.execute).to contain_exactly(subgroup, project)
end
it 'does not include subgroups the user does not have access to' do
subgroup.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
public_subgroup = create(:group, :public, parent: group, path: 'public-group')
other_subgroup = create(:group, :private, parent: group, path: 'visible-private-group')
other_user = create(:user)
other_subgroup.add_developer(other_user)
finder = described_class.new(current_user: other_user, parent_group: group)
expect(finder.execute).to contain_exactly(public_subgroup, other_subgroup)
end
it 'only includes public groups when no user is given' do
public_subgroup = create(:group, :public, parent: group)
_private_subgroup = create(:group, :private, parent: group)
finder = described_class.new(current_user: nil, parent_group: group)
expect(finder.execute).to contain_exactly(public_subgroup)
end
context 'when archived is `true`' do
let(:params) { { archived: 'true' } }
it 'includes archived projects in the count of subgroups' do
create(:project, namespace: subgroup, archived: true)
expect(finder.execute.first.preloaded_project_count).to eq(1)
end
end
context 'with a filter' do
let(:params) { { filter: 'test' } }
it 'contains only matching projects and subgroups' do
matching_project = create(:project, namespace: group, name: 'Testproject')
matching_subgroup = create(:group, name: 'testgroup', parent: group)
expect(finder.execute).to contain_exactly(matching_subgroup, matching_project)
end
it 'does not include subgroups the user does not have access to' do
subgroup.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
public_subgroup = create(:group, :public, parent: group, path: 'public-group')
other_subgroup = create(:group, :private, parent: group, path: 'visible-private-group')
_invisible_subgroup = create(:group, :private, parent: group, name: 'test1')
other_subgroup = create(:group, :private, parent: group, name: 'test2')
public_subgroup = create(:group, :public, parent: group, name: 'test3')
other_subsubgroup = create(:group, :private, parent: other_subgroup, name: 'test4')
other_user = create(:user)
other_subgroup.add_developer(other_user)
finder = described_class.new(current_user: other_user, parent_group: group)
finder = described_class.new(current_user: other_user,
parent_group: group,
params: params)
expect(finder.execute).to contain_exactly(public_subgroup, other_subgroup)
expect(finder.execute).to contain_exactly(other_subgroup, public_subgroup, other_subsubgroup)
end
it 'only includes public groups when no user is given' do
public_subgroup = create(:group, :public, parent: group)
_private_subgroup = create(:group, :private, parent: group)
context 'with matching children' do
it 'includes a group that has a subgroup matching the query and its parent' do
matching_subgroup = create(:group, :private, name: 'testgroup', parent: subgroup)
finder = described_class.new(current_user: nil, parent_group: group)
expect(finder.execute).to contain_exactly(public_subgroup)
end
context 'when archived is `true`' do
let(:params) { { archived: 'true' } }
it 'includes archived projects in the count of subgroups' do
create(:project, namespace: subgroup, archived: true)
expect(finder.execute.first.preloaded_project_count).to eq(1)
end
end
context 'with a filter' do
let(:params) { { filter: 'test' } }
it 'contains only matching projects and subgroups' do
matching_project = create(:project, namespace: group, name: 'Testproject')
matching_subgroup = create(:group, name: 'testgroup', parent: group)
expect(finder.execute).to contain_exactly(matching_subgroup, matching_project)
expect(finder.execute).to contain_exactly(subgroup, matching_subgroup)
end
it 'does not include subgroups the user does not have access to' do
_invisible_subgroup = create(:group, :private, parent: group, name: 'test1')
other_subgroup = create(:group, :private, parent: group, name: 'test2')
public_subgroup = create(:group, :public, parent: group, name: 'test3')
other_subsubgroup = create(:group, :private, parent: other_subgroup, name: 'test4')
other_user = create(:user)
other_subgroup.add_developer(other_user)
it 'includes the parent of a matching project' do
matching_project = create(:project, namespace: subgroup, name: 'Testproject')
finder = described_class.new(current_user: other_user,
parent_group: group,
params: params)
expect(finder.execute).to contain_exactly(other_subgroup, public_subgroup, other_subsubgroup)
expect(finder.execute).to contain_exactly(subgroup, matching_project)
end
context 'with matching children' do
it 'includes a group that has a subgroup matching the query and its parent' do
matching_subgroup = create(:group, :private, name: 'testgroup', parent: subgroup)
context 'with a small page size' do
let(:params) { { filter: 'test', per_page: 1 } }
expect(finder.execute).to contain_exactly(subgroup, matching_subgroup)
it 'contains all the ancestors of a matching subgroup regardless the page size' do
subgroup = create(:group, :private, parent: group)
matching = create(:group, :private, name: 'testgroup', parent: subgroup)
expect(finder.execute).to contain_exactly(subgroup, matching)
end
end
it 'includes the parent of a matching project' do
matching_project = create(:project, namespace: subgroup, name: 'Testproject')
it 'does not include the parent itself' do
group.update!(name: 'test')
expect(finder.execute).to contain_exactly(subgroup, matching_project)
end
context 'with a small page size' do
let(:params) { { filter: 'test', per_page: 1 } }
it 'contains all the ancestors of a matching subgroup regardless the page size' do
subgroup = create(:group, :private, parent: group)
matching = create(:group, :private, name: 'testgroup', parent: subgroup)
expect(finder.execute).to contain_exactly(subgroup, matching)
end
end
it 'does not include the parent itself' do
group.update!(name: 'test')
expect(finder.execute).not_to include(group)
end
expect(finder.execute).not_to include(group)
end
end
end
end
end
it_behaves_like 'group descentants finder examples'
context 'when feature flag :linear_group_descendants_finder is disabled' do
before do
stub_feature_flags(linear_group_descendants_finder: false)
end
it_behaves_like 'group descentants finder examples'
end
end

View File

@ -342,27 +342,30 @@ describe('Base editor', () => {
describe('implementation', () => {
let instance;
beforeEach(() => {
instance = editor.createInstance({ el: editorEl, blobPath, blobContent });
});
it('correctly proxies value from the model', () => {
instance = editor.createInstance({ el: editorEl, blobPath, blobContent });
expect(instance.getValue()).toBe(blobContent);
});
it('emits the EDITOR_READY_EVENT event after setting up the instance', () => {
it('emits the EDITOR_READY_EVENT event passing the instance after setting it up', () => {
jest.spyOn(monacoEditor, 'create').mockImplementation(() => {
return {
setModel: jest.fn(),
onDidDispose: jest.fn(),
layout: jest.fn(),
dispose: jest.fn(),
};
});
const eventSpy = jest.fn();
let passedInstance;
const eventSpy = jest.fn().mockImplementation((ev) => {
passedInstance = ev.detail.instance;
});
editorEl.addEventListener(EDITOR_READY_EVENT, eventSpy);
expect(eventSpy).not.toHaveBeenCalled();
editor.createInstance({ el: editorEl });
instance = editor.createInstance({ el: editorEl });
expect(eventSpy).toHaveBeenCalled();
expect(passedInstance).toBe(instance);
});
});

View File

@ -38,14 +38,14 @@ describe('Timezone Dropdown', () => {
const tzStr = '[UTC + 5.5] Sri Jayawardenepura';
const tzValue = 'Asia/Colombo';
expect($inputEl.val()).toBe('UTC');
expect($inputEl.val()).toBe('Etc/UTC');
$(`${tzListSel}:contains('${tzStr}')`, $wrapper).trigger('click');
const val = $inputEl.val();
expect(val).toBe(tzValue);
expect(val).not.toBe('UTC');
expect(val).not.toBe('Etc/UTC');
});
it('will format data array of timezones into a list of offsets', () => {
@ -67,7 +67,7 @@ describe('Timezone Dropdown', () => {
it('will default the timezone to UTC', () => {
const tz = $inputEl.val();
expect(tz).toBe('UTC');
expect(tz).toBe('Etc/UTC');
});
});

View File

@ -1,6 +1,5 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import DeleteBlobModal from '~/repository/components/delete_blob_modal.vue';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
@ -16,6 +15,7 @@ const DEFAULT_PROPS = {
projectPath: 'some/project/path',
isLocked: false,
canLock: true,
showForkSuggestion: false,
};
const DEFAULT_INJECT = {
@ -27,7 +27,7 @@ describe('BlobButtonGroup component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(BlobButtonGroup, {
wrapper = mountExtended(BlobButtonGroup, {
propsData: {
...DEFAULT_PROPS,
...props,
@ -35,9 +35,6 @@ describe('BlobButtonGroup component', () => {
provide: {
...DEFAULT_INJECT,
},
directives: {
GlModal: createMockDirective(),
},
});
};
@ -47,7 +44,8 @@ describe('BlobButtonGroup component', () => {
const findDeleteBlobModal = () => wrapper.findComponent(DeleteBlobModal);
const findUploadBlobModal = () => wrapper.findComponent(UploadBlobModal);
const findReplaceButton = () => wrapper.find('[data-testid="replace"]');
const findDeleteButton = () => wrapper.findByTestId('delete');
const findReplaceButton = () => wrapper.findByTestId('replace');
it('renders component', () => {
createComponent();
@ -63,6 +61,8 @@ describe('BlobButtonGroup component', () => {
describe('buttons', () => {
beforeEach(() => {
createComponent();
jest.spyOn(findUploadBlobModal().vm, 'show');
jest.spyOn(findDeleteBlobModal().vm, 'show');
});
it('renders both the replace and delete button', () => {
@ -75,10 +75,37 @@ describe('BlobButtonGroup component', () => {
});
it('triggers the UploadBlobModal from the replace button', () => {
const { value } = getBinding(findReplaceButton().element, 'gl-modal');
const modalId = findUploadBlobModal().props('modalId');
findReplaceButton().trigger('click');
expect(modalId).toEqual(value);
expect(findUploadBlobModal().vm.show).toHaveBeenCalled();
});
it('triggers the DeleteBlobModal from the delete button', () => {
findDeleteButton().trigger('click');
expect(findDeleteBlobModal().vm.show).toHaveBeenCalled();
});
describe('showForkSuggestion set to true', () => {
beforeEach(() => {
createComponent({ showForkSuggestion: true });
jest.spyOn(findUploadBlobModal().vm, 'show');
jest.spyOn(findDeleteBlobModal().vm, 'show');
});
it('does not trigger the UploadBlobModal from the replace button', () => {
findReplaceButton().trigger('click');
expect(findUploadBlobModal().vm.show).not.toHaveBeenCalled();
expect(wrapper.emitted().fork).toBeTruthy();
});
it('does not trigger the DeleteBlobModal from the delete button', () => {
findDeleteButton().trigger('click');
expect(findDeleteBlobModal().vm.show).not.toHaveBeenCalled();
expect(wrapper.emitted().fork).toBeTruthy();
});
});
});

View File

@ -56,52 +56,6 @@ RSpec.describe Banzai::Filter::FootnoteFilter do
it 'properly adds the necessary ids and classes' do
expect(doc.to_html).to eq filtered_footnote.strip
end
context 'using ruby-based HTML renderer' do
# first[^1] and second[^second]
# [^1]: one
# [^second]: two
let(:footnote) do
<<~EOF
<p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p>
<p>same reference<sup><a href="#fn1" id="fnref1">1</a></sup></p>
<ol>
<li id="fn1">
<p>one <a href="#fnref1"></a></p>
</li>
<li id="fn2">
<p>two <a href="#fnref2"></a></p>
</li>
</ol>
EOF
end
let(:filtered_footnote) do
<<~EOF
<p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p>
<p>same reference<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup></p>
<section class="footnotes"><ol>
<li id="fn1-#{identifier}">
<p>one <a href="#fnref1-#{identifier}" class="footnote-backref"></a></p>
</li>
<li id="fn2-#{identifier}">
<p>two <a href="#fnref2-#{identifier}" class="footnote-backref"></a></p>
</li>
</ol></section>
EOF
end
let(:doc) { filter(footnote) }
let(:identifier) { link_node[:id].delete_prefix('fnref1-') }
before do
stub_feature_flags(use_cmark_renderer: false)
end
it 'properly adds the necessary ids and classes' do
expect(doc.to_html).to eq filtered_footnote
end
end
end
context 'when detecting footnotes' do

View File

@ -5,125 +5,90 @@ require 'spec_helper'
RSpec.describe Banzai::Filter::MarkdownFilter do
include FilterSpecHelper
shared_examples_for 'renders correct markdown' do
describe 'markdown engine from context' do
it 'defaults to CommonMark' do
expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
expect(instance).to receive(:render).and_return('test')
end
filter('test')
describe 'markdown engine from context' do
it 'defaults to CommonMark' do
expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
expect(instance).to receive(:render).and_return('test')
end
it 'uses CommonMark' do
expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
expect(instance).to receive(:render).and_return('test')
end
filter('test', { markdown_engine: :common_mark })
end
filter('test')
end
describe 'code block' do
context 'using CommonMark' do
before do
stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark)
end
it 'adds language to lang attribute when specified' do
result = filter("```html\nsome code\n```", no_sourcepos: true)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
expect(result).to start_with('<pre lang="html"><code>')
else
expect(result).to start_with('<pre><code lang="html">')
end
end
it 'does not add language to lang attribute when not specified' do
result = filter("```\nsome code\n```", no_sourcepos: true)
expect(result).to start_with('<pre><code>')
end
it 'works with utf8 chars in language' do
result = filter("```日\nsome code\n```", no_sourcepos: true)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
expect(result).to start_with('<pre lang="日"><code>')
else
expect(result).to start_with('<pre><code lang="日">')
end
end
it 'works with additional language parameters' do
result = filter("```ruby:red gem foo\nsome code\n```", no_sourcepos: true)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
expect(result).to start_with('<pre lang="ruby:red" data-meta="gem foo"><code>')
else
expect(result).to start_with('<pre><code lang="ruby:red gem foo">')
end
end
it 'uses CommonMark' do
expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
expect(instance).to receive(:render).and_return('test')
end
filter('test', { markdown_engine: :common_mark })
end
end
describe 'source line position' do
context 'using CommonMark' do
before do
stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark)
end
it 'defaults to add data-sourcepos' do
result = filter('test')
expect(result).to eq '<p data-sourcepos="1:1-1:4">test</p>'
end
it 'disables data-sourcepos' do
result = filter('test', no_sourcepos: true)
expect(result).to eq '<p>test</p>'
end
describe 'code block' do
context 'using CommonMark' do
before do
stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark)
end
end
describe 'footnotes in tables' do
it 'processes footnotes in table cells' do
text = <<-MD.strip_heredoc
| Column1 |
| --------- |
| foot [^1] |
it 'adds language to lang attribute when specified' do
result = filter("```html\nsome code\n```", no_sourcepos: true)
[^1]: a footnote
MD
expect(result).to start_with('<pre lang="html"><code>')
end
result = filter(text, no_sourcepos: true)
it 'does not add language to lang attribute when not specified' do
result = filter("```\nsome code\n```", no_sourcepos: true)
expect(result).to include('<td>foot <sup')
expect(result).to start_with('<pre><code>')
end
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
expect(result).to include('<section class="footnotes" data-footnotes>')
else
expect(result).to include('<section class="footnotes">')
end
it 'works with utf8 chars in language' do
result = filter("```日\nsome code\n```", no_sourcepos: true)
expect(result).to start_with('<pre lang="日"><code>')
end
it 'works with additional language parameters' do
result = filter("```ruby:red gem foo\nsome code\n```", no_sourcepos: true)
expect(result).to start_with('<pre lang="ruby:red" data-meta="gem foo"><code>')
end
end
end
context 'using ruby-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: false)
end
describe 'source line position' do
context 'using CommonMark' do
before do
stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark)
end
it_behaves_like 'renders correct markdown'
it 'defaults to add data-sourcepos' do
result = filter('test')
expect(result).to eq '<p data-sourcepos="1:1-1:4">test</p>'
end
it 'disables data-sourcepos' do
result = filter('test', no_sourcepos: true)
expect(result).to eq '<p>test</p>'
end
end
end
context 'using c-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: true)
end
describe 'footnotes in tables' do
it 'processes footnotes in table cells' do
text = <<-MD.strip_heredoc
| Column1 |
| --------- |
| foot [^1] |
it_behaves_like 'renders correct markdown'
[^1]: a footnote
MD
result = filter(text, no_sourcepos: true)
expect(result).to include('<td>foot <sup')
expect(result).to include('<section class="footnotes" data-footnotes>')
end
end
end

View File

@ -5,67 +5,33 @@ require 'spec_helper'
RSpec.describe Banzai::Filter::PlantumlFilter do
include FilterSpecHelper
shared_examples_for 'renders correct markdown' do
it 'replaces plantuml pre tag with img tag' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
it 'replaces plantuml pre tag with img tag' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
input = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
'<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
else
'<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
end
input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>'
doc = filter(input)
output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>'
doc = filter(input)
expect(doc.to_s).to eq output
end
it 'does not replace plantuml pre tag with img tag if disabled' do
stub_application_setting(plantuml_enabled: false)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
output = '<pre lang="plantuml"><code>Bob -&gt; Sara : Hello</code></pre>'
else
input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
output = '<pre><code lang="plantuml">Bob -&gt; Sara : Hello</code></pre>'
end
doc = filter(input)
expect(doc.to_s).to eq output
end
it 'does not replace plantuml pre tag with img tag if url is invalid' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid")
input = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
'<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
else
'<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
end
output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> Error: cannot connect to PlantUML server at "invalid"</pre></div></div>'
doc = filter(input)
expect(doc.to_s).to eq output
end
expect(doc.to_s).to eq output
end
context 'using ruby-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: false)
end
it 'does not replace plantuml pre tag with img tag if disabled' do
stub_application_setting(plantuml_enabled: false)
it_behaves_like 'renders correct markdown'
input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
output = '<pre lang="plantuml"><code>Bob -&gt; Sara : Hello</code></pre>'
doc = filter(input)
expect(doc.to_s).to eq output
end
context 'using c-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: true)
end
it 'does not replace plantuml pre tag with img tag if url is invalid' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid")
it_behaves_like 'renders correct markdown'
input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> Error: cannot connect to PlantUML server at "invalid"</pre></div></div>'
doc = filter(input)
expect(doc.to_s).to eq output
end
end

View File

@ -177,53 +177,6 @@ RSpec.describe Banzai::Filter::SanitizationFilter do
expect(act.to_html).to eq exp
end
end
context 'using ruby-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: false)
end
it 'allows correct footnote id property on links' do
exp = %q(<a href="#fn1" id="fnref1">foo/bar.md</a>)
act = filter(exp)
expect(act.to_html).to eq exp
end
it 'allows correct footnote id property on li element' do
exp = %q(<ol><li id="fn1">footnote</li></ol>)
act = filter(exp)
expect(act.to_html).to eq exp
end
it 'removes invalid id for footnote links' do
exp = %q(<a href="#fn1">link</a>)
%w[fnrefx test xfnref1].each do |id|
act = filter(%(<a href="#fn1" id="#{id}">link</a>))
expect(act.to_html).to eq exp
end
end
it 'removes invalid id for footnote li' do
exp = %q(<ol><li>footnote</li></ol>)
%w[fnx test xfn1].each do |id|
act = filter(%(<ol><li id="#{id}">footnote</li></ol>))
expect(act.to_html).to eq exp
end
end
it 'allows footnotes numbered higher than 9' do
exp = %q(<a href="#fn15" id="fnref15">link</a><ol><li id="fn15">footnote</li></ol>)
act = filter(exp)
expect(act.to_html).to eq exp
end
end
end
end
end

View File

@ -19,202 +19,142 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
end
end
shared_examples_for 'renders correct markdown' do
context "when no language is specified" do
it "highlights as plaintext" do
result = filter('<pre><code>def fun end</code></pre>')
context "when no language is specified" do
it "highlights as plaintext" do
result = filter('<pre><code>def fun end</code></pre>')
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", ""
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre><copy-code></copy-code></div>')
end
context "when contains mermaid diagrams" do
it "ignores mermaid blocks" do
result = filter('<pre data-mermaid-style="display"><code>mermaid code</code></pre>')
include_examples "XSS prevention", ""
end
expect(result.to_html).to eq('<pre data-mermaid-style="display"><code>mermaid code</code></pre>')
end
end
context "when contains mermaid diagrams" do
it "ignores mermaid blocks" do
result = filter('<pre data-mermaid-style="display"><code>mermaid code</code></pre>')
context "when a valid language is specified" do
it "highlights as that language" do
result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
filter('<pre lang="ruby"><code>def fun end</code></pre>')
else
filter('<pre><code lang="ruby">def fun end</code></pre>')
end
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "ruby"
end
context "when an invalid language is specified" do
it "highlights as plaintext" do
result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
filter('<pre lang="gnuplot"><code>This is a test</code></pre>')
else
filter('<pre><code lang="gnuplot">This is a test</code></pre>')
end
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "gnuplot"
end
context "languages that should be passed through" do
let(:delimiter) { described_class::LANG_PARAMS_DELIMITER }
let(:data_attr) { described_class::LANG_PARAMS_ATTR }
%w(math mermaid plantuml suggestion).each do |lang|
context "when #{lang} is specified" do
it "highlights as plaintext but with the correct language attribute and class" do
result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
filter(%{<pre lang="#{lang}"><code>This is a test</code></pre>})
else
filter(%{<pre><code lang="#{lang}">This is a test</code></pre>})
end
expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>})
end
include_examples "XSS prevention", lang
end
context "when #{lang} has extra params" do
let(:lang_params) { 'foo-bar-kux' }
let(:xss_lang) do
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
"#{lang} data-meta=\"foo-bar-kux\"&lt;script&gt;alert(1)&lt;/script&gt;"
else
"#{lang}#{described_class::LANG_PARAMS_DELIMITER}&lt;script&gt;alert(1)&lt;/script&gt;"
end
end
it "includes data-lang-params tag with extra information" do
result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
filter(%{<pre lang="#{lang}" data-meta="#{lang_params}"><code>This is a test</code></pre>})
else
filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}">This is a test</code></pre>})
end
expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>})
end
include_examples "XSS prevention", lang
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
include_examples "XSS prevention",
"#{lang} data-meta=\"foo-bar-kux\"&lt;script&gt;alert(1)&lt;/script&gt;"
else
include_examples "XSS prevention",
"#{lang}#{described_class::LANG_PARAMS_DELIMITER}&lt;script&gt;alert(1)&lt;/script&gt;"
end
include_examples "XSS prevention",
"#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>"
end
end
context 'when multiple param delimiters are used' do
let(:lang) { 'suggestion' }
let(:lang_params) { '-1+10' }
let(:expected_result) do
%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params} more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>}
end
context 'when delimiter is space' do
it 'delimits on the first appearance' do
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params} more-things"><code>This is a test</code></pre>})
expect(result.to_html.delete("\n")).to eq(expected_result)
else
result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}#{delimiter}more-things">This is a test</code></pre>})
expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}#{delimiter}more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>})
end
end
end
context 'when delimiter is colon' do
it 'delimits on the first appearance' do
result = filter(%{<pre lang="#{lang}#{delimiter}#{lang_params} more-things"><code>This is a test</code></pre>})
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
expect(result.to_html.delete("\n")).to eq(expected_result)
else
expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class=\"code highlight js-syntax-highlight language-plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">This is a test</span></code></pre><copy-code></copy-code></div>})
end
end
end
end
end
context "when sourcepos metadata is available" do
it "includes it in the highlighted code block" do
result = filter('<pre data-sourcepos="1:1-3:3"><code lang="plaintext">This is a test</code></pre>')
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
end
end
context "when Rouge lexing fails" do
before do
allow_next_instance_of(Rouge::Lexers::Ruby) do |instance|
allow(instance).to receive(:stream_tokens).and_raise(StandardError)
end
end
it "highlights as plaintext" do
result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
filter('<pre lang="ruby"><code>This is a test</code></pre>')
else
filter('<pre><code lang="ruby">This is a test</code></pre>')
end
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "ruby"
end
context "when Rouge lexing fails after a retry" do
before do
allow_next_instance_of(Rouge::Lexers::PlainText) do |instance|
allow(instance).to receive(:stream_tokens).and_raise(StandardError)
end
end
it "does not add highlighting classes" do
result = filter('<pre><code>This is a test</code></pre>')
expect(result.to_html).to eq('<pre><code>This is a test</code></pre>')
end
include_examples "XSS prevention", "ruby"
expect(result.to_html).to eq('<pre data-mermaid-style="display"><code>mermaid code</code></pre>')
end
end
context 'using ruby-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: false)
context "when a valid language is specified" do
it "highlights as that language" do
result = filter('<pre lang="ruby"><code>def fun end</code></pre>')
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre><copy-code></copy-code></div>')
end
it_behaves_like 'renders correct markdown'
include_examples "XSS prevention", "ruby"
end
context 'using c-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: true)
context "when an invalid language is specified" do
it "highlights as plaintext" do
result = filter('<pre lang="gnuplot"><code>This is a test</code></pre>')
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
end
it_behaves_like 'renders correct markdown'
include_examples "XSS prevention", "gnuplot"
end
context "languages that should be passed through" do
let(:delimiter) { described_class::LANG_PARAMS_DELIMITER }
let(:data_attr) { described_class::LANG_PARAMS_ATTR }
%w(math mermaid plantuml suggestion).each do |lang|
context "when #{lang} is specified" do
it "highlights as plaintext but with the correct language attribute and class" do
result = filter(%{<pre lang="#{lang}"><code>This is a test</code></pre>})
expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>})
end
include_examples "XSS prevention", lang
end
context "when #{lang} has extra params" do
let(:lang_params) { 'foo-bar-kux' }
let(:xss_lang) { "#{lang} data-meta=\"foo-bar-kux\"&lt;script&gt;alert(1)&lt;/script&gt;" }
it "includes data-lang-params tag with extra information" do
result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params}"><code>This is a test</code></pre>})
expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>})
end
include_examples "XSS prevention", lang
include_examples "XSS prevention",
"#{lang} data-meta=\"foo-bar-kux\"&lt;script&gt;alert(1)&lt;/script&gt;"
include_examples "XSS prevention",
"#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>"
end
end
context 'when multiple param delimiters are used' do
let(:lang) { 'suggestion' }
let(:lang_params) { '-1+10' }
let(:expected_result) do
%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params} more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>}
end
context 'when delimiter is space' do
it 'delimits on the first appearance' do
result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params} more-things"><code>This is a test</code></pre>})
expect(result.to_html.delete("\n")).to eq(expected_result)
end
end
context 'when delimiter is colon' do
it 'delimits on the first appearance' do
result = filter(%{<pre lang="#{lang}#{delimiter}#{lang_params} more-things"><code>This is a test</code></pre>})
expect(result.to_html.delete("\n")).to eq(expected_result)
end
end
end
end
context "when sourcepos metadata is available" do
it "includes it in the highlighted code block" do
result = filter('<pre data-sourcepos="1:1-3:3"><code lang="plaintext">This is a test</code></pre>')
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
end
end
context "when Rouge lexing fails" do
before do
allow_next_instance_of(Rouge::Lexers::Ruby) do |instance|
allow(instance).to receive(:stream_tokens).and_raise(StandardError)
end
end
it "highlights as plaintext" do
result = filter('<pre lang="ruby"><code>This is a test</code></pre>')
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "ruby"
end
context "when Rouge lexing fails after a retry" do
before do
allow_next_instance_of(Rouge::Lexers::PlainText) do |instance|
allow(instance).to receive(:stream_tokens).and_raise(StandardError)
end
end
it "does not add highlighting classes" do
result = filter('<pre><code>This is a test</code></pre>')
expect(result.to_html).to eq('<pre><code>This is a test</code></pre>')
end
include_examples "XSS prevention", "ruby"
end
end

View File

@ -65,47 +65,6 @@ RSpec.describe Banzai::Pipeline::FullPipeline do
expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote.strip
end
context 'using ruby-based HTML renderer' do
let(:html) { described_class.to_html(footnote_markdown, project: project) }
let(:identifier) { html[/.*fnref1-(\d+).*/, 1] }
let(:footnote_markdown) do
<<~EOF
first[^1] and second[^second] and twenty[^twenty]
[^1]: one
[^second]: two
[^twenty]: twenty
EOF
end
let(:filtered_footnote) do
<<~EOF
<p dir="auto">first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup> and twenty<sup class="footnote-ref"><a href="#fn3-#{identifier}" id="fnref3-#{identifier}">3</a></sup></p>
<section class="footnotes"><ol>
<li id="fn1-#{identifier}">
<p>one <a href="#fnref1-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1"></gl-emoji></a></p>
</li>
<li id="fn2-#{identifier}">
<p>two <a href="#fnref2-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1"></gl-emoji></a></p>
</li>
<li id="fn3-#{identifier}">
<p>twenty <a href="#fnref3-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1"></gl-emoji></a></p>
</li>
</ol></section>
EOF
end
before do
stub_feature_flags(use_cmark_renderer: false)
end
it 'properly adds the necessary ids and classes' do
stub_commonmark_sourcepos_disabled
expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote
end
end
end
describe 'links are detected as malicious' do

View File

@ -5,117 +5,93 @@ require 'spec_helper'
RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline do
using RSpec::Parameterized::TableSyntax
shared_examples_for 'renders correct markdown' do
describe 'CommonMark tests', :aggregate_failures do
it 'converts all reference punctuation to literals' do
reference_chars = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS
markdown = reference_chars.split('').map {|char| char.prepend("\\") }.join
punctuation = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS.split('')
punctuation = punctuation.delete_if {|char| char == '&' }
punctuation << '&amp;'
result = described_class.call(markdown, project: project)
output = result[:output].to_html
punctuation.each { |char| expect(output).to include("<span>#{char}</span>") }
expect(result[:escaped_literals]).to be_truthy
end
it 'ensure we handle all the GitLab reference characters', :eager_load do
reference_chars = ObjectSpace.each_object(Class).map do |klass|
next unless klass.included_modules.include?(Referable)
next unless klass.respond_to?(:reference_prefix)
next unless klass.reference_prefix.length == 1
klass.reference_prefix
end.compact
reference_chars.all? do |char|
Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS.include?(char)
end
end
it 'does not convert non-reference punctuation to spans' do
markdown = %q(\"\'\*\+\,\-\.\/\:\;\<\=\>\?\[\]\_\`\{\|\}) + %q[\(\)\\\\]
result = described_class.call(markdown, project: project)
output = result[:output].to_html
expect(output).not_to include('<span>')
expect(result[:escaped_literals]).to be_falsey
end
it 'does not convert other characters to literals' do
markdown = %q(\\A\a\ \3\φ\«)
expected = '\→\A\a\ \3\φ\«'
result = correct_html_included(markdown, expected)
expect(result[:escaped_literals]).to be_falsey
end
describe 'backslash escapes do not work in code blocks, code spans, autolinks, or raw HTML' do
where(:markdown, :expected) do
%q(`` \@\! ``) | %q(<code>\@\!</code>)
%q( \@\!) | %Q(<code>\\@\\!\n</code>)
%Q(~~~\n\\@\\!\n~~~) | %Q(<code>\\@\\!\n</code>)
%q(<http://example.com?find=\@>) | %q(<a href="http://example.com?find=%5C@">http://example.com?find=\@</a>)
%q[<a href="/bar\@)">] | %q[<a href="/bar%5C@)">]
end
with_them do
it { correct_html_included(markdown, expected) }
end
end
describe 'work in all other contexts, including URLs and link titles, link references, and info strings in fenced code blocks' do
let(:markdown) { %Q(``` foo\\@bar\nfoo\n```) }
it 'renders correct html' do
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
correct_html_included(markdown, %Q(<pre data-sourcepos="1:1-3:3" lang="foo@bar"><code>foo\n</code></pre>))
else
correct_html_included(markdown, %Q(<code lang="foo@bar">foo\n</code>))
end
end
where(:markdown, :expected) do
%q![foo](/bar\@ "\@title")! | %q(<a href="/bar@" title="@title">foo</a>)
%Q![foo]\n\n[foo]: /bar\\@ "\\@title"! | %q(<a href="/bar@" title="@title">foo</a>)
end
with_them do
it { correct_html_included(markdown, expected) }
end
end
end
end
describe 'backslash escapes' do
describe 'backslash escapes', :aggregate_failures do
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }
def correct_html_included(markdown, expected)
result = described_class.call(markdown, {})
it 'converts all reference punctuation to literals' do
reference_chars = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS
markdown = reference_chars.split('').map {|char| char.prepend("\\") }.join
punctuation = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS.split('')
punctuation = punctuation.delete_if {|char| char == '&' }
punctuation << '&amp;'
expect(result[:output].to_html).to include(expected)
result = described_class.call(markdown, project: project)
output = result[:output].to_html
result
punctuation.each { |char| expect(output).to include("<span>#{char}</span>") }
expect(result[:escaped_literals]).to be_truthy
end
context 'using ruby-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: false)
end
it 'ensure we handle all the GitLab reference characters', :eager_load do
reference_chars = ObjectSpace.each_object(Class).map do |klass|
next unless klass.included_modules.include?(Referable)
next unless klass.respond_to?(:reference_prefix)
next unless klass.reference_prefix.length == 1
it_behaves_like 'renders correct markdown'
klass.reference_prefix
end.compact
reference_chars.all? do |char|
Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS.include?(char)
end
end
context 'using c-based HTML renderer' do
before do
stub_feature_flags(use_cmark_renderer: true)
it 'does not convert non-reference punctuation to spans' do
markdown = %q(\"\'\*\+\,\-\.\/\:\;\<\=\>\?\[\]\_\`\{\|\}) + %q[\(\)\\\\]
result = described_class.call(markdown, project: project)
output = result[:output].to_html
expect(output).not_to include('<span>')
expect(result[:escaped_literals]).to be_falsey
end
it 'does not convert other characters to literals' do
markdown = %q(\\A\a\ \3\φ\«)
expected = '\→\A\a\ \3\φ\«'
result = correct_html_included(markdown, expected)
expect(result[:escaped_literals]).to be_falsey
end
describe 'backslash escapes do not work in code blocks, code spans, autolinks, or raw HTML' do
where(:markdown, :expected) do
%q(`` \@\! ``) | %q(<code>\@\!</code>)
%q( \@\!) | %Q(<code>\\@\\!\n</code>)
%Q(~~~\n\\@\\!\n~~~) | %Q(<code>\\@\\!\n</code>)
%q(<http://example.com?find=\@>) | %q(<a href="http://example.com?find=%5C@">http://example.com?find=\@</a>)
%q[<a href="/bar\@)">] | %q[<a href="/bar%5C@)">]
end
it_behaves_like 'renders correct markdown'
with_them do
it { correct_html_included(markdown, expected) }
end
end
describe 'work in all other contexts, including URLs and link titles, link references, and info strings in fenced code blocks' do
let(:markdown) { %Q(``` foo\\@bar\nfoo\n```) }
it 'renders correct html' do
correct_html_included(markdown, %Q(<pre data-sourcepos="1:1-3:3" lang="foo@bar"><code>foo\n</code></pre>))
end
where(:markdown, :expected) do
%q![foo](/bar\@ "\@title")! | %q(<a href="/bar@" title="@title">foo</a>)
%Q![foo]\n\n[foo]: /bar\\@ "\\@title"! | %q(<a href="/bar@" title="@title">foo</a>)
end
with_them do
it { correct_html_included(markdown, expected) }
end
end
end
def correct_html_included(markdown, expected)
result = described_class.call(markdown, {})
expect(result[:output].to_html).to include(expected)
result
end
end

File diff suppressed because it is too large Load Diff

View File

@ -292,45 +292,84 @@ RSpec.describe Gitlab::MergeRequests::CommitMessageGenerator do
context 'when project has merge commit template with approvers' do
let(:user1) { create(:user) }
let(:user2) { create(:user) }
let(message_template_name) do
"Merge Request approved by:\n%{approved_by}"
end
let(message_template_name) { <<~MSG.rstrip }
Merge branch '%{source_branch}' into '%{target_branch}'
context "and mr has no approval" do
%{approved_by}
MSG
context 'and mr has no approval' do
before do
merge_request.approved_by_users = []
end
it "returns empty string" do
it 'removes variable and blank line' do
expect(result_message).to eq <<~MSG.rstrip
Merge Request approved by:
Merge branch 'feature' into 'master'
MSG
end
context 'when there is blank line after approved_by' do
let(message_template_name) { <<~MSG.rstrip }
Merge branch '%{source_branch}' into '%{target_branch}'
%{approved_by}
Type: merge
MSG
it 'removes blank line before it' do
expect(result_message).to eq <<~MSG.rstrip
Merge branch 'feature' into 'master'
Type: merge
MSG
end
end
context 'when there is no blank line after approved_by' do
let(message_template_name) { <<~MSG.rstrip }
Merge branch '%{source_branch}' into '%{target_branch}'
%{approved_by}
Type: merge
MSG
it 'does not remove blank line before it' do
expect(result_message).to eq <<~MSG.rstrip
Merge branch 'feature' into 'master'
Type: merge
MSG
end
end
end
context "and mr has one approval" do
context 'and mr has one approval' do
before do
merge_request.approved_by_users = [user1]
end
it "returns user name and email" do
it 'returns user name and email' do
expect(result_message).to eq <<~MSG.rstrip
Merge Request approved by:
Approved-by: #{user1.name} <#{user1.email}>
Merge branch 'feature' into 'master'
Approved-by: #{user1.name} <#{user1.email}>
MSG
end
end
context "and mr has multiple approvals" do
context 'and mr has multiple approvals' do
before do
merge_request.approved_by_users = [user1, user2]
end
it "returns users names and emails" do
it 'returns users names and emails' do
expect(result_message).to eq <<~MSG.rstrip
Merge Request approved by:
Approved-by: #{user1.name} <#{user1.email}>
Approved-by: #{user2.name} <#{user2.email}>
Merge branch 'feature' into 'master'
Approved-by: #{user1.name} <#{user1.email}>
Approved-by: #{user2.name} <#{user2.email}>
MSG
end
end

View File

@ -92,12 +92,7 @@ module StubGitlabCalls
end
def stub_commonmark_sourcepos_disabled
render_options =
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS_C
else
Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS_RUBY
end
render_options = Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS
allow_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark)
.to receive(:render_options)