Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
e540c0d7
Commit
e540c0d7
authored
Apr 04, 2019
by
Oswaldo Ferreira
Committed by
Douwe Maan
Apr 04, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixed test specs
- added suggestions to mock data - fixed props to be not required
parent
30988aec
Changes
36
Show whitespace changes
Inline
Side-by-side
Showing
36 changed files
with
755 additions
and
267 deletions
+755
-267
app/assets/javascripts/diffs/components/diff_line_note_form.vue
...sets/javascripts/diffs/components/diff_line_note_form.vue
+5
-1
app/assets/javascripts/notes/components/note_form.vue
app/assets/javascripts/notes/components/note_form.vue
+41
-3
app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js
...javascripts/vue_shared/components/lib/utils/diff_utils.js
+20
-0
app/assets/javascripts/vue_shared/components/markdown/field.vue
...sets/javascripts/vue_shared/components/markdown/field.vue
+2
-3
app/assets/javascripts/vue_shared/components/markdown/header.vue
...ets/javascripts/vue_shared/components/markdown/header.vue
+1
-1
app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
...cripts/vue_shared/components/markdown/suggestion_diff.vue
+13
-29
app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
...ts/vue_shared/components/markdown/suggestion_diff_row.vue
+32
-0
app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
...avascripts/vue_shared/components/markdown/suggestions.vue
+3
-35
app/controllers/concerns/preview_markdown.rb
app/controllers/concerns/preview_markdown.rb
+1
-1
app/serializers/issue_entity.rb
app/serializers/issue_entity.rb
+1
-1
app/serializers/merge_request_widget_entity.rb
app/serializers/merge_request_widget_entity.rb
+1
-1
app/serializers/suggestion_entity.rb
app/serializers/suggestion_entity.rb
+2
-0
app/serializers/suggestion_serializer.rb
app/serializers/suggestion_serializer.rb
+9
-0
app/services/concerns/suggestible.rb
app/services/concerns/suggestible.rb
+7
-0
app/services/preview_markdown_service.rb
app/services/preview_markdown_service.rb
+20
-8
app/views/shared/form_elements/_description.html.haml
app/views/shared/form_elements/_description.html.haml
+1
-1
app/views/shared/notes/_form.html.haml
app/views/shared/notes/_form.html.haml
+1
-1
changelogs/unreleased/osw-support-multi-line-suggestions.yml
changelogs/unreleased/osw-support-multi-line-suggestions.yml
+5
-0
doc/user/discussions/img/multi-line-suggestion-preview.png
doc/user/discussions/img/multi-line-suggestion-preview.png
+0
-0
doc/user/discussions/img/multi-line-suggestion-syntax.png
doc/user/discussions/img/multi-line-suggestion-syntax.png
+0
-0
doc/user/discussions/index.md
doc/user/discussions/index.md
+18
-0
lib/banzai/suggestions_parser.rb
lib/banzai/suggestions_parser.rb
+0
-16
spec/controllers/projects_controller_spec.rb
spec/controllers/projects_controller_spec.rb
+10
-0
spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
...tures/merge_request/user_suggests_changes_on_diff_spec.rb
+104
-7
spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
...ue_shared/components/markdown/suggestion_diff_row_spec.js
+98
-0
spec/javascripts/notes/mock_data.js
spec/javascripts/notes/mock_data.js
+2
-4
spec/javascripts/vue_shared/components/markdown/header_spec.js
...javascripts/vue_shared/components/markdown/header_spec.js
+1
-1
spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
...ts/vue_shared/components/markdown/suggestion_diff_spec.js
+44
-22
spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
...cripts/vue_shared/components/markdown/suggestions_spec.js
+45
-64
spec/lib/banzai/suggestions_parser_spec.rb
spec/lib/banzai/suggestions_parser_spec.rb
+0
-32
spec/lib/gitlab/diff/suggestion_spec.rb
spec/lib/gitlab/diff/suggestion_spec.rb
+76
-11
spec/lib/gitlab/diff/suggestions_parser_spec.rb
spec/lib/gitlab/diff/suggestions_parser_spec.rb
+61
-0
spec/models/suggestion_spec.rb
spec/models/suggestion_spec.rb
+16
-0
spec/serializers/suggestion_entity_spec.rb
spec/serializers/suggestion_entity_spec.rb
+1
-2
spec/services/preview_markdown_service_spec.rb
spec/services/preview_markdown_service_spec.rb
+61
-12
spec/services/suggestions/apply_service_spec.rb
spec/services/suggestions/apply_service_spec.rb
+53
-11
No files found.
app/assets/javascripts/diffs/components/diff_line_note_form.vue
View file @
e540c0d7
...
...
@@ -48,10 +48,13 @@ export default {
noteableType
:
this
.
noteableType
,
noteTargetLine
:
this
.
noteTargetLine
,
diffViewType
:
this
.
diffViewType
,
diffFile
:
this
.
getDiffFileByHash
(
this
.
diffFileHash
)
,
diffFile
:
this
.
diffFile
,
linePosition
:
this
.
linePosition
,
};
},
diffFile
()
{
return
this
.
getDiffFileByHash
(
this
.
diffFileHash
);
},
},
mounted
()
{
if
(
this
.
isLoggedIn
)
{
...
...
@@ -102,6 +105,7 @@ export default {
:line-code=
"line.line_code"
:line=
"line"
:help-page-path=
"helpPagePath"
:diff-file=
"diffFile"
save-button-title=
"Comment"
class=
"diff-comment-form"
@
handleFormUpdateAddToReview=
"addToReview"
...
...
app/assets/javascripts/notes/components/note_form.vue
View file @
e540c0d7
...
...
@@ -61,6 +61,11 @@ export default {
required
:
false
,
default
:
null
,
},
diffFile
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
...
...
@@ -102,9 +107,42 @@ export default {
}
return
'
#
'
;
},
diffParams
()
{
if
(
this
.
diffFile
)
{
return
{
filePath
:
this
.
diffFile
.
file_path
,
refs
:
this
.
diffFile
.
diff_refs
,
};
}
else
if
(
this
.
note
&&
this
.
note
.
position
)
{
return
{
filePath
:
this
.
note
.
position
.
new_path
,
refs
:
this
.
note
.
position
,
};
}
else
if
(
this
.
discussion
&&
this
.
discussion
.
diff_file
)
{
return
{
filePath
:
this
.
discussion
.
diff_file
.
file_path
,
refs
:
this
.
discussion
.
diff_file
.
diff_refs
,
};
}
return
null
;
},
markdownPreviewPath
()
{
const
notable
=
this
.
getNoteableDataByProp
(
'
preview_note_path
'
);
return
mergeUrlParams
({
preview_suggestions
:
true
},
notable
);
const
previewSuggestions
=
this
.
line
&&
this
.
diffParams
;
const
params
=
previewSuggestions
?
{
preview_suggestions
:
previewSuggestions
,
line
:
this
.
line
.
new_line
,
file_path
:
this
.
diffParams
.
filePath
,
base_sha
:
this
.
diffParams
.
refs
.
base_sha
,
start_sha
:
this
.
diffParams
.
refs
.
start_sha
,
head_sha
:
this
.
diffParams
.
refs
.
head_sha
,
}
:
{};
return
mergeUrlParams
(
params
,
notable
);
},
markdownDocsPath
()
{
return
this
.
getNotesDataByProp
(
'
markdownDocsPath
'
);
...
...
@@ -234,8 +272,8 @@ export default {
placeholder=
"Write a comment or drag your files here…"
@
keydown.meta.enter=
"handleKeySubmit()"
@
keydown.ctrl.enter=
"handleKeySubmit()"
@
keydown.up=
"editMyLastNote()"
@
keydown.esc=
"cancelHandler(true)"
@
keydown.
exact.
up=
"editMyLastNote()"
@
keydown.e
xact.e
sc=
"cancelHandler(true)"
@
input=
"onInput"
></textarea>
</markdown-field>
...
...
app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js
0 → 100644
View file @
e540c0d7
/* eslint-disable import/prefer-default-export */
function
trimFirstCharOfLineContent
(
text
)
{
if
(
!
text
)
{
return
text
;
}
return
text
.
replace
(
/^
(
|
\+
|-
)
/
,
''
);
}
function
cleanSuggestionLine
(
line
=
{})
{
return
{
...
line
,
text
:
trimFirstCharOfLineContent
(
line
.
text
),
};
}
export
function
selectDiffLines
(
lines
)
{
return
lines
.
filter
(
line
=>
line
.
type
!==
'
match
'
).
map
(
line
=>
cleanSuggestionLine
(
line
));
}
app/assets/javascripts/vue_shared/components/markdown/field.vue
View file @
e540c0d7
...
...
@@ -76,6 +76,7 @@ export default {
hasSuggestion
:
false
,
markdownPreviewLoading
:
false
,
previewMarkdown
:
false
,
suggestions
:
this
.
note
.
suggestions
||
[],
};
},
computed
:
{
...
...
@@ -109,9 +110,6 @@ export default {
}
return
lineNumber
;
},
suggestions
()
{
return
this
.
note
.
suggestions
||
[];
},
lineType
()
{
return
this
.
line
?
this
.
line
.
type
:
''
;
},
...
...
@@ -175,6 +173,7 @@ export default {
this
.
referencedCommands
=
data
.
references
.
commands
;
this
.
referencedUsers
=
data
.
references
.
users
;
this
.
hasSuggestion
=
data
.
references
.
suggestions
&&
data
.
references
.
suggestions
.
length
;
this
.
suggestions
=
data
.
references
.
suggestions
;
}
this
.
$nextTick
()
...
...
app/assets/javascripts/vue_shared/components/markdown/header.vue
View file @
e540c0d7
...
...
@@ -38,7 +38,7 @@ export default {
].
join
(
'
\n
'
);
},
mdSuggestion
()
{
return
[
'
```suggestion
'
,
`{text}`
,
'
```
'
].
join
(
'
\n
'
);
return
[
'
```suggestion
:-0+0
'
,
`{text}`
,
'
```
'
].
join
(
'
\n
'
);
},
},
mounted
()
{
...
...
app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
View file @
e540c0d7
<
script
>
import
SuggestionDiffHeader
from
'
./suggestion_diff_header.vue
'
;
import
SuggestionDiffRow
from
'
./suggestion_diff_row.vue
'
;
import
{
selectDiffLines
}
from
'
../lib/utils/diff_utils
'
;
export
default
{
components
:
{
SuggestionDiffHeader
,
SuggestionDiffRow
,
},
props
:
{
newLines
:
{
type
:
Array
,
required
:
true
,
},
fromContent
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
fromLine
:
{
type
:
Number
,
required
:
true
,
},
suggestion
:
{
type
:
Object
,
required
:
true
,
...
...
@@ -33,6 +23,11 @@ export default {
required
:
true
,
},
},
computed
:
{
lines
()
{
return
selectDiffLines
(
this
.
suggestion
.
diff_lines
);
},
},
methods
:
{
applySuggestion
(
callback
)
{
this
.
$emit
(
'
apply
'
,
{
suggestionId
:
this
.
suggestion
.
id
,
callback
});
...
...
@@ -52,22 +47,11 @@ export default {
/>
<table
class=
"mb-3 md-suggestion-diff js-syntax-highlight code"
>
<tbody>
<!-- Old Line -->
<tr
class=
"line_holder old"
>
<td
class=
"diff-line-num old_line qa-old-diff-line-number old"
>
{{
fromLine
}}
</td>
<td
class=
"diff-line-num new_line old"
></td>
<td
class=
"line_content old"
>
<span>
{{
fromContent
}}
</span>
</td>
</tr>
<!-- New Line(s) -->
<tr
v-for=
"(line, key) of newLines"
:key=
"key"
class=
"line_holder new"
>
<td
class=
"diff-line-num old_line new"
></td>
<td
class=
"diff-line-num new_line qa-new-diff-line-number new"
>
{{
line
.
lineNumber
}}
</td>
<td
class=
"line_content new"
>
<span>
{{
line
.
content
}}
</span>
</td>
</tr>
<suggestion-diff-row
v-for=
"(line, index) of lines"
:key=
"`$
{index}-${line.text}`"
:line="line"
/>
</tbody>
</table>
</div>
...
...
app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
0 → 100644
View file @
e540c0d7
<
script
>
export
default
{
name
:
'
SuggestionDiffRow
'
,
props
:
{
line
:
{
type
:
Object
,
required
:
true
,
},
},
computed
:
{
lineType
()
{
return
this
.
line
.
type
;
},
},
};
</
script
>
<
template
>
<tr
class=
"line_holder"
:class=
"lineType"
>
<td
class=
"diff-line-num old_line"
:class=
"lineType"
>
{{
line
.
old_line
}}
</td>
<td
class=
"diff-line-num new_line"
:class=
"lineType"
>
{{
line
.
new_line
}}
</td>
<td
class=
"line_content"
:class=
"lineType"
>
<span
v-if=
"line.text"
>
{{
line
.
text
}}
</span>
<!-- TODO: replace this hack with zero-width whitespace when we have rich_text from BE -->
<span
v-else
>
​
</span>
</td>
</tr>
</
template
>
app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
View file @
e540c0d7
...
...
@@ -6,16 +6,6 @@ import Flash from '~/flash';
export
default
{
components
:
{
SuggestionDiff
},
props
:
{
fromLine
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
fromContent
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
lineType
:
{
type
:
String
,
required
:
false
,
...
...
@@ -71,41 +61,19 @@ export default {
suggestionElements
.
forEach
((
suggestionEl
,
i
)
=>
{
const
suggestionParentEl
=
suggestionEl
.
parentElement
;
const
newLines
=
this
.
extractNewLines
(
suggestionParentEl
);
const
diffComponent
=
this
.
generateDiff
(
newLines
,
i
);
const
diffComponent
=
this
.
generateDiff
(
i
);
diffComponent
.
$mount
(
suggestionParentEl
);
});
this
.
isRendered
=
true
;
},
extractNewLines
(
suggestionEl
)
{
// extracts the suggested lines from the markdown
// calculates a line number for each line
const
newLines
=
suggestionEl
.
querySelectorAll
(
'
.line
'
);
const
fromLine
=
this
.
suggestions
.
length
?
this
.
suggestions
[
0
].
from_line
:
this
.
fromLine
;
const
lines
=
[];
newLines
.
forEach
((
line
,
i
)
=>
{
const
content
=
`
${
line
.
innerText
}
\n`
;
const
lineNumber
=
fromLine
+
i
;
lines
.
push
({
content
,
lineNumber
});
});
return
lines
;
},
generateDiff
(
newLines
,
suggestionIndex
)
{
// generates the diff
<
suggestion
-
diff
/>
component
// all `suggestion` markdown will be swapped out by this component
generateDiff
(
suggestionIndex
)
{
const
{
suggestions
,
disabled
,
helpPagePath
}
=
this
;
const
suggestion
=
suggestions
&&
suggestions
[
suggestionIndex
]
?
suggestions
[
suggestionIndex
]
:
{};
const
fromContent
=
suggestion
.
from_content
||
this
.
fromContent
;
const
fromLine
=
suggestion
.
from_line
||
this
.
fromLine
;
const
SuggestionDiffComponent
=
Vue
.
extend
(
SuggestionDiff
);
const
suggestionDiff
=
new
SuggestionDiffComponent
({
propsData
:
{
newLines
,
fromLine
,
fromContent
,
disabled
,
suggestion
,
helpPagePath
},
propsData
:
{
disabled
,
suggestion
,
helpPagePath
},
});
suggestionDiff
.
$on
(
'
apply
'
,
({
suggestionId
,
callback
})
=>
{
...
...
app/controllers/concerns/preview_markdown.rb
View file @
e540c0d7
...
...
@@ -20,7 +20,7 @@ module PreviewMarkdown
body:
view_context
.
markdown
(
result
[
:text
],
markdown_params
),
references:
{
users:
result
[
:users
],
suggestions:
result
[
:suggestions
]
,
suggestions:
SuggestionSerializer
.
new
.
represent_diff
(
result
[
:suggestions
])
,
commands:
view_context
.
markdown
(
result
[
:commands
])
}
}
...
...
app/serializers/issue_entity.rb
View file @
e540c0d7
...
...
@@ -42,6 +42,6 @@ class IssueEntity < IssuableEntity
end
expose
:preview_note_path
do
|
issue
|
preview_markdown_path
(
issue
.
project
,
quick_actions_target_type:
'Issue'
,
quick_actions_
target_id:
issue
.
iid
)
preview_markdown_path
(
issue
.
project
,
target_type:
'Issue'
,
target_id:
issue
.
iid
)
end
end
app/serializers/merge_request_widget_entity.rb
View file @
e540c0d7
...
...
@@ -235,7 +235,7 @@ class MergeRequestWidgetEntity < IssuableEntity
end
expose
:preview_note_path
do
|
merge_request
|
preview_markdown_path
(
merge_request
.
project
,
quick_actions_target_type:
'MergeRequest'
,
quick_actions_
target_id:
merge_request
.
iid
)
preview_markdown_path
(
merge_request
.
project
,
target_type:
'MergeRequest'
,
target_id:
merge_request
.
iid
)
end
expose
:merge_commit_path
do
|
merge_request
|
...
...
app/serializers/suggestion_entity.rb
View file @
e540c0d7
...
...
@@ -3,6 +3,8 @@
class
SuggestionEntity
<
API
::
Entities
::
Suggestion
include
RequestAwareEntity
unexpose
:from_line
,
:to_line
,
:from_content
,
:to_content
expose
:diff_lines
,
using:
DiffLineEntity
expose
:current_user
do
expose
:can_apply
do
|
suggestion
|
Ability
.
allowed?
(
current_user
,
:apply_suggestion
,
suggestion
)
...
...
app/serializers/suggestion_serializer.rb
0 → 100644
View file @
e540c0d7
# frozen_string_literal: true
class
SuggestionSerializer
<
BaseSerializer
entity
SuggestionEntity
def
represent_diff
(
resource
)
represent
(
resource
,
{
only:
[
:diff_lines
]
})
end
end
app/services/concerns/suggestible.rb
View file @
e540c0d7
...
...
@@ -2,10 +2,17 @@
module
Suggestible
extend
ActiveSupport
::
Concern
include
Gitlab
::
Utils
::
StrongMemoize
# This translates into limiting suggestion changes to `suggestion:-100+100`.
MAX_LINES_CONTEXT
=
100
.
freeze
def
diff_lines
strong_memoize
(
:diff_lines
)
do
Gitlab
::
Diff
::
SuggestionDiff
.
new
(
self
).
diff_lines
end
end
def
fetch_from_content
diff_file
.
new_blob_lines_between
(
from_line
,
to_line
).
join
end
...
...
app/services/preview_markdown_service.rb
View file @
e540c0d7
...
...
@@ -17,7 +17,7 @@ class PreviewMarkdownService < BaseService
private
def
explain_quick_actions
(
text
)
return
text
,
[]
unless
%w(Issue MergeRequest Commit)
.
include?
(
commands_
target_type
)
return
text
,
[]
unless
%w(Issue MergeRequest Commit)
.
include?
(
target_type
)
quick_actions_service
=
QuickActions
::
InterpretService
.
new
(
project
,
current_user
)
quick_actions_service
.
explain
(
text
,
find_commands_target
)
...
...
@@ -30,22 +30,34 @@ class PreviewMarkdownService < BaseService
end
def
find_suggestions
(
text
)
return
[]
unless
p
arams
[
:preview_suggestions
]
return
[]
unless
p
review_sugestions?
Banzai
::
SuggestionsParser
.
parse
(
text
)
position
=
Gitlab
::
Diff
::
Position
.
new
(
new_path:
params
[
:file_path
],
new_line:
params
[
:line
].
to_i
,
base_sha:
params
[
:base_sha
],
head_sha:
params
[
:head_sha
],
start_sha:
params
[
:start_sha
])
Gitlab
::
Diff
::
SuggestionsParser
.
parse
(
text
,
position:
position
,
project:
project
)
end
def
preview_sugestions?
params
[
:preview_suggestions
]
&&
target_type
==
'MergeRequest'
&&
Ability
.
allowed?
(
current_user
,
:download_code
,
project
)
end
def
find_commands_target
QuickActions
::
TargetService
.
new
(
project
,
current_user
)
.
execute
(
commands_target_type
,
commands_
target_id
)
.
execute
(
target_type
,
target_id
)
end
def
commands_
target_type
params
[
:
quick_actions_
target_type
]
def
target_type
params
[
:target_type
]
end
def
commands_
target_id
params
[
:
quick_actions_
target_id
]
def
target_id
params
[
:target_id
]
end
end
app/views/shared/form_elements/_description.html.haml
View file @
e540c0d7
...
...
@@ -5,7 +5,7 @@
-
supports_quick_actions
=
model
.
new_record?
-
if
supports_quick_actions
-
preview_url
=
preview_markdown_path
(
project
,
quick_actions_
target_type:
model
.
class
.
name
)
-
preview_url
=
preview_markdown_path
(
project
,
target_type:
model
.
class
.
name
)
-
else
-
preview_url
=
preview_markdown_path
(
project
)
...
...
app/views/shared/notes/_form.html.haml
View file @
e540c0d7
-
supports_autocomplete
=
local_assigns
.
fetch
(
:supports_autocomplete
,
true
)
-
supports_quick_actions
=
note_supports_quick_actions?
(
@note
)
-
if
supports_quick_actions
-
preview_url
=
preview_markdown_path
(
@project
,
quick_actions_target_type:
@note
.
noteable_type
,
quick_actions_
target_id:
@note
.
noteable_id
)
-
preview_url
=
preview_markdown_path
(
@project
,
target_type:
@note
.
noteable_type
,
target_id:
@note
.
noteable_id
)
-
else
-
preview_url
=
preview_markdown_path
(
@project
)
...
...
changelogs/unreleased/osw-support-multi-line-suggestions.yml
0 → 100644
View file @
e540c0d7
---
title
:
Support multi-line suggestions
merge_request
:
25211
author
:
type
:
added
doc/user/discussions/img/multi-line-suggestion-preview.png
0 → 100644
View file @
e540c0d7
60.2 KB
doc/user/discussions/img/multi-line-suggestion-syntax.png
0 → 100644
View file @
e540c0d7
29.1 KB
doc/user/discussions/index.md
View file @
e540c0d7
...
...
@@ -344,6 +344,24 @@ and push the suggested change directly into the codebase in the merge request's
Custom commit messages will be introduced by
[
#54404
](
https://gitlab.com/gitlab-org/gitlab-ce/issues/54404
)
.
### Multi-line suggestions
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53310) in GitLab 11.10.
Reviewers can also suggest changes to
multiple lines with a single suggestion within Merge Request diff discussions.
![
Multi-line suggestion syntax
](
img/multi-line-suggestion-syntax.png
)
In the example above, the suggestion covers three lines above and four lines below the commented diff line.
It'd change from 3 lines _above_ to 4 lines _below_ the commented Diff line.
![
Multi-line suggestion preview
](
img/multi-line-suggestion-preview.png
)
NOTE:
**Note:**
Suggestions covering multiple lines are limited to 100 lines _above_ and 100 lines _below_
the commented diff line, allowing up to 200 changed lines per suggestion.
## Start a discussion by replying to a standard comment
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30299) in GitLab 11.9
...
...
lib/banzai/suggestions_parser.rb
deleted
100644 → 0
View file @
30988aec
# frozen_string_literal: true
# TODO: Delete when https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26107
# exchange this parser by `Gitlab::Diff::SuggestionsParser`.
module
Banzai
module
SuggestionsParser
# Returns the content of each suggestion code block.
#
def
self
.
parse
(
text
)
html
=
Banzai
.
render
(
text
,
project:
nil
,
no_original_data:
true
)
doc
=
Nokogiri
::
HTML
(
html
)
doc
.
search
(
'pre.suggestion'
).
map
{
|
node
|
node
.
text
}
end
end
end
spec/controllers/projects_controller_spec.rb
View file @
e540c0d7
...
...
@@ -703,6 +703,16 @@ describe ProjectsController do
expect
(
JSON
.
parse
(
response
.
body
).
keys
).
to
match_array
(
%w(body references)
)
end
context
'when not authorized'
do
let
(
:private_project
)
{
create
(
:project
,
:private
)
}
it
'returns 404'
do
post
:preview_markdown
,
params:
{
namespace_id:
private_project
.
namespace
,
id:
private_project
,
text:
'*Markdown* text'
}
expect
(
response
).
to
have_gitlab_http_status
(
404
)
end
end
context
'state filter on references'
do
let
(
:issue
)
{
create
(
:issue
,
:closed
,
project:
public_project
)
}
let
(
:merge_request
)
{
create
(
:merge_request
,
:closed
,
target_project:
public_project
)
}
...
...
spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
View file @
e540c0d7
...
...
@@ -6,6 +6,14 @@ describe 'User comments on a diff', :js do
include
MergeRequestDiffHelpers
include
RepoHelpers
def
expect_suggestion_has_content
(
element
,
expected_changing_content
,
expected_suggested_content
)
changing_content
=
element
.
all
(
:css
,
'.line_holder.old'
).
map
(
&
:text
)
suggested_content
=
element
.
all
(
:css
,
'.line_holder.new'
).
map
(
&
:text
)
expect
(
changing_content
).
to
eq
(
expected_changing_content
)
expect
(
suggested_content
).
to
eq
(
expected_suggested_content
)
end
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:merge_request
)
do
create
(
:merge_request_with_diffs
,
source_project:
project
,
target_project:
project
,
source_branch:
'merge-test'
)
...
...
@@ -33,8 +41,18 @@ describe 'User comments on a diff', :js do
page
.
within
(
'.diff-discussions'
)
do
expect
(
page
).
to
have_button
(
'Apply suggestion'
)
expect
(
page
).
to
have_content
(
'Suggested change'
)
expect
(
page
).
to
have_content
(
' url = https://github.com/gitlabhq/gitlab-shell.git'
)
expect
(
page
).
to
have_content
(
'# change to a comment'
)
end
page
.
within
(
'.md-suggestion-diff'
)
do
expected_changing_content
=
[
"6 url = https://github.com/gitlabhq/gitlab-shell.git"
]
expected_suggested_content
=
[
"6 # change to a comment"
]
expect_suggestion_has_content
(
page
,
expected_changing_content
,
expected_suggested_content
)
end
end
...
...
@@ -64,7 +82,7 @@ describe 'User comments on a diff', :js do
click_diff_line
(
find
(
"[id='
#{
sample_compare
.
changes
[
1
][
:line_code
]
}
']"
))
page
.
within
(
'.js-discussion-note-form'
)
do
fill_in
(
'note_note'
,
with:
"```suggestion
\n
# change to a comment
\n
```
\n
```suggestion
\n
# or that
\n
```"
)
fill_in
(
'note_note'
,
with:
"```suggestion
\n
# change to a comment
\n
```
\n
```suggestion
:-2
\n
# or that
\n
# heh
\n
```"
)
click_button
(
'Comment'
)
end
...
...
@@ -74,11 +92,90 @@ describe 'User comments on a diff', :js do
suggestion_1
=
page
.
all
(
:css
,
'.md-suggestion-diff'
)[
0
]
suggestion_2
=
page
.
all
(
:css
,
'.md-suggestion-diff'
)[
1
]
expect
(
suggestion_1
).
to
have_content
(
' url = https://github.com/gitlabhq/gitlab-shell.git'
)
expect
(
suggestion_1
).
to
have_content
(
'# change to a comment'
)
suggestion_1_expected_changing_content
=
[
"6 url = https://github.com/gitlabhq/gitlab-shell.git"
]
suggestion_1_expected_suggested_content
=
[
"6 # change to a comment"
]
suggestion_2_expected_changing_content
=
[
"4 [submodule
\"
gitlab-shell
\"
]"
,
"5 path = gitlab-shell"
,
"6 url = https://github.com/gitlabhq/gitlab-shell.git"
]
suggestion_2_expected_suggested_content
=
[
"4 # or that"
,
"5 # heh"
]
expect_suggestion_has_content
(
suggestion_1
,
suggestion_1_expected_changing_content
,
suggestion_1_expected_suggested_content
)
expect_suggestion_has_content
(
suggestion_2
,
suggestion_2_expected_changing_content
,
suggestion_2_expected_suggested_content
)
end
end
end
context
'multi-line suggestions'
do
it
'suggestion is presented'
do
click_diff_line
(
find
(
"[id='
#{
sample_compare
.
changes
[
1
][
:line_code
]
}
']"
))
page
.
within
(
'.js-discussion-note-form'
)
do
fill_in
(
'note_note'
,
with:
"```suggestion:-3+5
\n
# change to a
\n
# comment
\n
# with
\n
# broken
\n
# lines
\n
```"
)
click_button
(
'Comment'
)
end
expect
(
suggestion_2
).
to
have_content
(
' url = https://github.com/gitlabhq/gitlab-shell.git'
)
expect
(
suggestion_2
).
to
have_content
(
'# or that'
)
wait_for_requests
page
.
within
(
'.diff-discussions'
)
do
expect
(
page
).
to
have_button
(
'Apply suggestion'
)
expect
(
page
).
to
have_content
(
'Suggested change'
)
end
page
.
within
(
'.md-suggestion-diff'
)
do
expected_changing_content
=
[
"3 url = git://github.com/randx/six.git"
,
"4 [submodule
\"
gitlab-shell
\"
]"
,
"5 path = gitlab-shell"
,
"6 url = https://github.com/gitlabhq/gitlab-shell.git"
,
"7 [submodule
\"
gitlab-grack
\"
]"
,
"8 path = gitlab-grack"
,
"9 url = https://gitlab.com/gitlab-org/gitlab-grack.git"
]
expected_suggested_content
=
[
"3 # change to a"
,
"4 # comment"
,
"5 # with"
,
"6 # broken"
,
"7 # lines"
]
expect_suggestion_has_content
(
page
,
expected_changing_content
,
expected_suggested_content
)
end
end
it
'suggestion is appliable'
do
click_diff_line
(
find
(
"[id='
#{
sample_compare
.
changes
[
1
][
:line_code
]
}
']"
))
page
.
within
(
'.js-discussion-note-form'
)
do
fill_in
(
'note_note'
,
with:
"```suggestion:-3+5
\n
# change to a
\n
# comment
\n
# with
\n
# broken
\n
# lines
\n
```"
)
click_button
(
'Comment'
)
end
wait_for_requests
page
.
within
(
'.diff-discussions'
)
do
expect
(
page
).
not_to
have_content
(
'Applied'
)
click_button
(
'Apply suggestion'
)
wait_for_requests
expect
(
page
).
to
have_content
(
'Applied'
)
end
end
end
...
...
spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
0 → 100644
View file @
e540c0d7
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
SuggestionDiffRow
from
'
~/vue_shared/components/markdown/suggestion_diff_row.vue
'
;
const
oldLine
=
{
can_receive_suggestion
:
false
,
line_code
:
null
,
meta_data
:
null
,
new_line
:
null
,
old_line
:
5
,
rich_text
:
'
-oldtext
'
,
text
:
'
-oldtext
'
,
type
:
'
old
'
,
};
const
newLine
=
{
can_receive_suggestion
:
false
,
line_code
:
null
,
meta_data
:
null
,
new_line
:
6
,
old_line
:
null
,
rich_text
:
'
-newtext
'
,
text
:
'
-newtext
'
,
type
:
'
new
'
,
};
describe
(
SuggestionDiffRow
.
name
,
()
=>
{
let
wrapper
;
const
factory
=
(
options
=
{})
=>
{
const
localVue
=
createLocalVue
();
wrapper
=
shallowMount
(
SuggestionDiffRow
,
{
localVue
,
...
options
,
});
};
const
findOldLineWrapper
=
()
=>
wrapper
.
find
(
'
.old_line
'
);
const
findNewLineWrapper
=
()
=>
wrapper
.
find
(
'
.new_line
'
);
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders correctly
'
,
()
=>
{
factory
({
propsData
:
{
line
:
oldLine
,
},
});
expect
(
wrapper
.
is
(
'
.line_holder
'
)).
toBe
(
true
);
});
describe
(
'
when passed line has type old
'
,
()
=>
{
beforeEach
(()
=>
{
factory
({
propsData
:
{
line
:
oldLine
,
},
});
});
it
(
'
has old class when line has type old
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
td
'
).
classes
()).
toContain
(
'
old
'
);
});
it
(
'
has old line number rendered
'
,
()
=>
{
expect
(
findOldLineWrapper
().
text
()).
toBe
(
'
5
'
);
});
it
(
'
has no new line number rendered
'
,
()
=>
{
expect
(
findNewLineWrapper
().
text
()).
toBe
(
''
);
});
});
describe
(
'
when passed line has type new
'
,
()
=>
{
beforeEach
(()
=>
{
factory
({
propsData
:
{
line
:
newLine
,
},
});
});
it
(
'
has new class when line has type new
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
td
'
).
classes
()).
toContain
(
'
new
'
);
});
it
(
'
has no old line number rendered
'
,
()
=>
{
expect
(
findOldLineWrapper
().
text
()).
toBe
(
''
);
});
it
(
'
has no new line number rendered
'
,
()
=>
{
expect
(
findNewLineWrapper
().
text
()).
toBe
(
'
6
'
);
});
});
});
spec/javascripts/notes/mock_data.js
View file @
e540c0d7
...
...
@@ -44,8 +44,7 @@ export const noteableDataMock = {
milestone
:
null
,
milestone_id
:
null
,
moved_to_id
:
null
,
preview_note_path
:
'
/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue
'
,
preview_note_path
:
'
/gitlab-org/gitlab-ce/preview_markdown?target_id=98&target_type=Issue
'
,
project_id
:
2
,
state
:
'
opened
'
,
time_estimate
:
0
,
...
...
@@ -347,8 +346,7 @@ export const loggedOutnoteableData = {
},
noteable_note_url
:
'
/group/project/merge_requests/1#note_1
'
,
create_note_path
:
'
/gitlab-org/gitlab-ce/notes?target_id=98&target_type=issue
'
,
preview_note_path
:
'
/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue
'
,
preview_note_path
:
'
/gitlab-org/gitlab-ce/preview_markdown?target_id=98&target_type=Issue
'
,
};
export
const
collapseNotesMock
=
[
...
...
spec/javascripts/vue_shared/components/markdown/header_spec.js
View file @
e540c0d7
...
...
@@ -98,7 +98,7 @@ describe('Markdown field header component', () => {
it
(
'
renders suggestion template
'
,
()
=>
{
vm
.
lineContent
=
'
Some content
'
;
expect
(
vm
.
mdSuggestion
).
toEqual
(
'
```suggestion
\n
{text}
\n
```
'
);
expect
(
vm
.
mdSuggestion
).
toEqual
(
'
```suggestion
:-0+0
\n
{text}
\n
```
'
);
});
it
(
'
does not render suggestion button if `canSuggest` is set to false
'
,
()
=>
{
...
...
spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
View file @
e540c0d7
import
Vue
from
'
vue
'
;
import
SuggestionDiffComponent
from
'
~/vue_shared/components/markdown/suggestion_diff.vue
'
;
import
{
selectDiffLines
}
from
'
~/vue_shared/components/lib/utils/diff_utils
'
;
const
MOCK_DATA
=
{
canApply
:
true
,
newLines
:
[
{
content
:
'
Line 1
\n
'
,
lineNumber
:
1
},
{
content
:
'
Line 2
\n
'
,
lineNumber
:
2
},
{
content
:
'
Line 3
\n
'
,
lineNumber
:
3
},
],
fromLine
:
1
,
fromContent
:
'
Old content
'
,
suggestion
:
{
id
:
1
,
diff_lines
:
[
{
can_receive_suggestion
:
false
,
line_code
:
null
,
meta_data
:
null
,
new_line
:
null
,
old_line
:
5
,
rich_text
:
'
-test
'
,
text
:
'
-test
'
,
type
:
'
old
'
,
},
{
can_receive_suggestion
:
true
,
line_code
:
null
,
meta_data
:
null
,
new_line
:
5
,
old_line
:
null
,
rich_text
:
'
+new test
'
,
text
:
'
+new test
'
,
type
:
'
new
'
,
},
{
can_receive_suggestion
:
true
,
line_code
:
null
,
meta_data
:
null
,
new_line
:
5
,
old_line
:
null
,
rich_text
:
'
+new test2
'
,
text
:
'
+new test2
'
,
type
:
'
new
'
,
},
],
},
helpPagePath
:
'
path_to_docs
'
,
};
const
lines
=
selectDiffLines
(
MOCK_DATA
.
suggestion
.
diff_lines
);
const
newLines
=
lines
.
filter
(
line
=>
line
.
type
===
'
new
'
);
describe
(
'
Suggestion Diff component
'
,
()
=>
{
let
vm
;
...
...
@@ -39,30 +68,23 @@ describe('Suggestion Diff component', () => {
});
it
(
'
renders the oldLineNumber
'
,
()
=>
{
const
fromLine
=
vm
.
$el
.
querySelector
(
'
.
qa-old-diff-line-number
'
).
innerHTML
;
const
fromLine
=
vm
.
$el
.
querySelector
(
'
.
old_line
'
).
innerHTML
;
expect
(
parseInt
(
fromLine
,
10
)).
toBe
(
vm
.
fromL
ine
);
expect
(
parseInt
(
fromLine
,
10
)).
toBe
(
lines
[
0
].
old_l
ine
);
});
it
(
'
renders the oldLineContent
'
,
()
=>
{
const
fromContent
=
vm
.
$el
.
querySelector
(
'
.line_content.old
'
).
innerHTML
;
expect
(
fromContent
.
includes
(
vm
.
fromContent
)).
toBe
(
true
);
});
it
(
'
renders the contents of newLines
'
,
()
=>
{
const
newLines
=
vm
.
$el
.
querySelectorAll
(
'
.line_holder.new
'
);
newLines
.
forEach
((
line
,
i
)
=>
{
expect
(
newLines
[
i
].
innerHTML
.
includes
(
vm
.
newLines
[
i
].
content
)).
toBe
(
true
);
});
expect
(
fromContent
.
includes
(
lines
[
0
].
text
)).
toBe
(
true
);
});
it
(
'
renders
a line number for each line
'
,
()
=>
{
const
newLine
Numbers
=
vm
.
$el
.
querySelectorAll
(
'
.qa-new-diff-line-number
'
);
it
(
'
renders
new lines
'
,
()
=>
{
const
newLine
sElements
=
vm
.
$el
.
querySelectorAll
(
'
.line_holder.new
'
);
newLineNumbers
.
forEach
((
line
,
i
)
=>
{
expect
(
newLineNumbers
[
i
].
innerHTML
.
includes
(
vm
.
newLines
[
i
].
lineNumber
)).
toBe
(
true
);
newLinesElements
.
forEach
((
line
,
i
)
=>
{
expect
(
newLinesElements
[
i
].
innerHTML
.
includes
(
newLines
[
i
].
new_line
)).
toBe
(
true
);
expect
(
newLinesElements
[
i
].
innerHTML
.
includes
(
newLines
[
i
].
text
)).
toBe
(
true
);
});
});
});
...
...
spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
View file @
e540c0d7
...
...
@@ -2,46 +2,52 @@ import Vue from 'vue';
import
SuggestionsComponent
from
'
~/vue_shared/components/markdown/suggestions.vue
'
;
const
MOCK_DATA
=
{
fromLine
:
1
,
fromContent
:
'
Old content
'
,
suggestions
:
[],
suggestions
:
[
{
id
:
1
,
appliable
:
true
,
applied
:
false
,
current_user
:
{
can_apply
:
true
,
},
diff_lines
:
[
{
can_receive_suggestion
:
false
,
line_code
:
null
,
meta_data
:
null
,
new_line
:
null
,
old_line
:
5
,
rich_text
:
'
-test
'
,
text
:
'
-test
'
,
type
:
'
old
'
,
},
{
can_receive_suggestion
:
true
,
line_code
:
null
,
meta_data
:
null
,
new_line
:
5
,
old_line
:
null
,
rich_text
:
'
+new test
'
,
text
:
'
+new test
'
,
type
:
'
new
'
,
},
],
},
],
noteHtml
:
`
<div class="suggestion">
<div class="line">
Suggestion 1
</div>
<div class="line">
-oldtest
</div>
</div>
<div class="suggestion">
<div class="line">
Suggestion 2
</div>
<div class="line">
+newtest
</div>
</div>
`
,
isApplied
:
false
,
helpPagePath
:
'
path_to_docs
'
,
};
const
generateLine
=
content
=>
{
const
line
=
document
.
createElement
(
'
div
'
);
line
.
className
=
'
line
'
;
line
.
innerHTML
=
content
;
return
line
;
};
const
generateMockLines
=
()
=>
{
const
line1
=
generateLine
(
'
Line 1
'
);
const
line2
=
generateLine
(
'
Line 2
'
);
const
line3
=
generateLine
(
'
- Line 3
'
);
const
container
=
document
.
createElement
(
'
div
'
);
container
.
appendChild
(
line1
);
container
.
appendChild
(
line2
);
container
.
appendChild
(
line3
);
return
container
;
};
describe
(
'
Suggestion component
'
,
()
=>
{
let
vm
;
let
extractedLines
;
let
diffTable
;
beforeEach
(
done
=>
{
...
...
@@ -51,8 +57,7 @@ describe('Suggestion component', () => {
propsData
:
MOCK_DATA
,
}).
$mount
();
extractedLines
=
vm
.
extractNewLines
(
generateMockLines
());
diffTable
=
vm
.
generateDiff
(
extractedLines
).
$mount
().
$el
;
diffTable
=
vm
.
generateDiff
(
0
).
$mount
().
$el
;
spyOn
(
vm
,
'
renderSuggestions
'
);
vm
.
renderSuggestions
();
...
...
@@ -70,32 +75,8 @@ describe('Suggestion component', () => {
it
(
'
renders suggestions
'
,
()
=>
{
expect
(
vm
.
renderSuggestions
).
toHaveBeenCalled
();
expect
(
vm
.
$el
.
innerHTML
.
includes
(
'
Suggestion 1
'
)).
toBe
(
true
);
expect
(
vm
.
$el
.
innerHTML
.
includes
(
'
Suggestion 2
'
)).
toBe
(
true
);
});
});
describe
(
'
extractNewLines
'
,
()
=>
{
it
(
'
extracts suggested lines
'
,
()
=>
{
const
expectedReturn
=
[
{
content
:
'
Line 1
\n
'
,
lineNumber
:
1
},
{
content
:
'
Line 2
\n
'
,
lineNumber
:
2
},
{
content
:
'
- Line 3
\n
'
,
lineNumber
:
3
},
];
expect
(
vm
.
extractNewLines
(
generateMockLines
())).
toEqual
(
expectedReturn
);
});
it
(
'
increments line number for each extracted line
'
,
()
=>
{
expect
(
extractedLines
[
0
].
lineNumber
).
toEqual
(
1
);
expect
(
extractedLines
[
1
].
lineNumber
).
toEqual
(
2
);
expect
(
extractedLines
[
2
].
lineNumber
).
toEqual
(
3
);
});
it
(
'
returns empty array if no lines are found
'
,
()
=>
{
const
el
=
document
.
createElement
(
'
div
'
);
expect
(
vm
.
extractNewLines
(
el
)).
toEqual
([]);
expect
(
vm
.
$el
.
innerHTML
.
includes
(
'
oldtest
'
)).
toBe
(
true
);
expect
(
vm
.
$el
.
innerHTML
.
includes
(
'
newtest
'
)).
toBe
(
true
);
});
});
...
...
@@ -109,17 +90,17 @@ describe('Suggestion component', () => {
});
it
(
'
generates a diff table that contains contents the suggested lines
'
,
()
=>
{
extractedLines
.
forEach
((
line
,
i
)
=>
{
expect
(
diffTable
.
innerHTML
.
includes
(
extractedLines
[
i
].
content
)).
toBe
(
true
);
MOCK_DATA
.
suggestions
[
0
].
diff_lines
.
forEach
(
line
=>
{
const
text
=
line
.
text
.
substring
(
1
);
expect
(
diffTable
.
innerHTML
.
includes
(
text
)).
toBe
(
true
);
});
});
it
(
'
generates a diff table with the correct line number for each suggested line
'
,
()
=>
{
const
lines
=
diffTable
.
getElementsByClassName
(
'
qa-new-diff-line-number
'
);
const
lines
=
diffTable
.
querySelectorAll
(
'
.old_line
'
);
expect
([...
lines
][
0
].
innerHTML
).
toBe
(
'
1
'
);
expect
([...
lines
][
1
].
innerHTML
).
toBe
(
'
2
'
);
expect
([...
lines
][
2
].
innerHTML
).
toBe
(
'
3
'
);
expect
(
parseInt
([...
lines
][
0
].
innerHTML
,
10
)).
toBe
(
5
);
});
});
});
spec/lib/banzai/suggestions_parser_spec.rb
deleted
100644 → 0
View file @
30988aec
# frozen_string_literal: true
require
'spec_helper'
describe
Banzai
::
SuggestionsParser
do
describe
'.parse'
do
it
'returns a list of suggestion contents'
do
markdown
=
<<-
MARKDOWN
.
strip_heredoc
```suggestion
foo
bar
```
```
nothing
```
```suggestion
xpto
baz
```
```thing
this is not a suggestion, it's a thing
```
MARKDOWN
expect
(
described_class
.
parse
(
markdown
)).
to
eq
([
" foo
\n
bar"
,
" xpto
\n
baz"
])
end
end
end
spec/lib/gitlab/diff/suggestion_spec.rb
View file @
e540c0d7
...
...
@@ -10,6 +10,16 @@ describe Gitlab::Diff::Suggestion do
lines_above:
above
,
lines_below:
below
)
end
it
'returns diff lines with correct line numbers'
do
diff_lines
=
suggestion
.
diff_lines
expect
(
diff_lines
).
to
all
(
be_a
(
Gitlab
::
Diff
::
Line
))
expected_diff_lines
.
each_with_index
do
|
expected_line
,
index
|
expect
(
diff_lines
[
index
].
to_hash
).
to
include
(
expected_line
)
end
end
end
let
(
:merge_request
)
{
create
(
:merge_request
)
}
...
...
@@ -48,6 +58,18 @@ describe Gitlab::Diff::Suggestion do
let
(
:expected_above
)
{
line
-
1
}
let
(
:expected_below
)
{
below
}
let
(
:expected_lines
)
{
blob_lines_data
(
line
-
expected_above
,
line
+
expected_below
)
}
let
(
:expected_diff_lines
)
do
[
{
old_pos:
1
,
new_pos:
1
,
type:
'old'
,
text:
"-require 'fileutils'"
},
{
old_pos:
2
,
new_pos:
1
,
type:
'old'
,
text:
"-require 'open3'"
},
{
old_pos:
3
,
new_pos:
1
,
type:
'old'
,
text:
"-"
},
{
old_pos:
4
,
new_pos:
1
,
type:
'old'
,
text:
"-module Popen"
},
{
old_pos:
5
,
new_pos:
1
,
type:
'old'
,
text:
"- extend self"
},
{
old_pos:
6
,
new_pos:
1
,
type:
'old'
,
text:
"-"
},
{
old_pos:
7
,
new_pos:
1
,
type:
'new'
,
text:
"+# parsed suggestion content"
},
{
old_pos:
7
,
new_pos:
2
,
type:
'new'
,
text:
"+# with comments"
}
]
end
it_behaves_like
'correct suggestion raw content'
end
...
...
@@ -59,6 +81,47 @@ describe Gitlab::Diff::Suggestion do
let
(
:expected_below
)
{
below
}
let
(
:expected_above
)
{
above
}
let
(
:expected_lines
)
{
blob_lines_data
(
line
-
expected_above
,
line
+
expected_below
)
}
let
(
:expected_diff_lines
)
do
[
{
old_pos:
4
,
new_pos:
4
,
type:
"match"
,
text:
"@@ -4 +4"
},
{
old_pos:
4
,
new_pos:
4
,
type:
"old"
,
text:
"-module Popen"
},
{
old_pos:
5
,
new_pos:
4
,
type:
"old"
,
text:
"- extend self"
},
{
old_pos:
6
,
new_pos:
4
,
type:
"old"
,
text:
"-"
},
{
old_pos:
7
,
new_pos:
4
,
type:
"old"
,
text:
"- def popen(cmd, path=nil)"
},
{
old_pos:
8
,
new_pos:
4
,
type:
"old"
,
text:
"- unless cmd.is_a?(Array)"
},
{
old_pos:
9
,
new_pos:
4
,
type:
"old"
,
text:
"- raise RuntimeError,
\"
System commands must be given as an array of strings
\"
"
},
{
old_pos:
10
,
new_pos:
4
,
type:
"old"
,
text:
"- end"
},
{
old_pos:
11
,
new_pos:
4
,
type:
"old"
,
text:
"-"
},
{
old_pos:
12
,
new_pos:
4
,
type:
"old"
,
text:
"- path ||= Dir.pwd"
},
{
old_pos:
13
,
new_pos:
4
,
type:
"old"
,
text:
"-"
},
{
old_pos:
14
,
new_pos:
4
,
type:
"old"
,
text:
"- vars = {"
},
{
old_pos:
15
,
new_pos:
4
,
type:
"old"
,
text:
"-
\"
PWD
\"
=> path"
},
{
old_pos:
16
,
new_pos:
4
,
type:
"old"
,
text:
"- }"
},
{
old_pos:
17
,
new_pos:
4
,
type:
"old"
,
text:
"-"
},
{
old_pos:
18
,
new_pos:
4
,
type:
"old"
,
text:
"- options = {"
},
{
old_pos:
19
,
new_pos:
4
,
type:
"old"
,
text:
"- chdir: path"
},
{
old_pos:
20
,
new_pos:
4
,
type:
"old"
,
text:
"- }"
},
{
old_pos:
21
,
new_pos:
4
,
type:
"old"
,
text:
"-"
},
{
old_pos:
22
,
new_pos:
4
,
type:
"old"
,
text:
"- unless File.directory?(path)"
},
{
old_pos:
23
,
new_pos:
4
,
type:
"old"
,
text:
"- FileUtils.mkdir_p(path)"
},
{
old_pos:
24
,
new_pos:
4
,
type:
"old"
,
text:
"- end"
},
{
old_pos:
25
,
new_pos:
4
,
type:
"old"
,
text:
"-"
},
{
old_pos:
26
,
new_pos:
4
,
type:
"old"
,
text:
"- @cmd_output =
\"\"
"
},
{
old_pos:
27
,
new_pos:
4
,
type:
"old"
,
text:
"- @cmd_status = 0"
},
{
old_pos:
28
,
new_pos:
4
,
type:
"old"
,
text:
"-"
},
{
old_pos:
29
,
new_pos:
4
,
type:
"old"
,
text:
"- Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|"
},
{
old_pos:
30
,
new_pos:
4
,
type:
"old"
,
text:
"- @cmd_output << stdout.read"
},
{
old_pos:
31
,
new_pos:
4
,
type:
"old"
,
text:
"- @cmd_output << stderr.read"
},
{
old_pos:
32
,
new_pos:
4
,
type:
"old"
,
text:
"- @cmd_status = wait_thr.value.exitstatus"
},
{
old_pos:
33
,
new_pos:
4
,
type:
"old"
,
text:
"- end"
},
{
old_pos:
34
,
new_pos:
4
,
type:
"old"
,
text:
"-"
},
{
old_pos:
35
,
new_pos:
4
,
type:
"old"
,
text:
"- return @cmd_output, @cmd_status"
},
{
old_pos:
36
,
new_pos:
4
,
type:
"old"
,
text:
"- end"
},
{
old_pos:
37
,
new_pos:
4
,
type:
"old"
,
text:
"-end"
},
{
old_pos:
38
,
new_pos:
4
,
type:
"new"
,
text:
"+# parsed suggestion content"
},
{
old_pos:
38
,
new_pos:
5
,
type:
"new"
,
text:
"+# with comments"
}
]
end
it_behaves_like
'correct suggestion raw content'
end
...
...
@@ -70,18 +133,20 @@ describe Gitlab::Diff::Suggestion do
let
(
:expected_below
)
{
below
}
let
(
:expected_above
)
{
above
}
let
(
:expected_lines
)
{
blob_lines_data
(
line
-
expected_above
,
line
+
expected_below
)
}
it_behaves_like
'correct suggestion raw content'
let
(
:expected_diff_lines
)
do
[
{
old_pos:
3
,
new_pos:
3
,
type:
"match"
,
text:
"@@ -3 +3"
},
{
old_pos:
3
,
new_pos:
3
,
type:
"old"
,
text:
"-"
},
{
old_pos:
4
,
new_pos:
3
,
type:
"old"
,
text:
"-module Popen"
},
{
old_pos:
5
,
new_pos:
3
,
type:
"old"
,
text:
"- extend self"
},
{
old_pos:
6
,
new_pos:
3
,
type:
"old"
,
text:
"-"
},
{
old_pos:
7
,
new_pos:
3
,
type:
"old"
,
text:
"- def popen(cmd, path=nil)"
},
{
old_pos:
8
,
new_pos:
3
,
type:
"old"
,
text:
"- unless cmd.is_a?(Array)"
},
{
old_pos:
9
,
new_pos:
3
,
type:
"new"
,
text:
"+# parsed suggestion content"
},
{
old_pos:
9
,
new_pos:
4
,
type:
"new"
,
text:
"+# with comments"
}
]
end
context
'when no extra lines (single-line suggestion)'
do
let
(
:line
)
{
5
}
let
(
:above
)
{
0
}
let
(
:below
)
{
0
}
let
(
:expected_below
)
{
below
}
let
(
:expected_above
)
{
above
}
let
(
:expected_lines
)
{
blob_lines_data
(
line
-
expected_above
,
line
+
expected_below
)
}
it_behaves_like
'correct suggestion raw content'
end
end
...
...
spec/lib/gitlab/diff/suggestions_parser_spec.rb
View file @
e540c0d7
...
...
@@ -69,5 +69,66 @@ describe Gitlab::Diff::SuggestionsParser do
lines_below:
0
)
end
end
context
'multi-line suggestions'
do
let
(
:markdown
)
do
<<-
MARKDOWN
.
strip_heredoc
```suggestion:-2+1
# above and below
```
```
nothing
```
```suggestion:-3
# only above
```
```suggestion:+3
# only below
```
```thing
this is not a suggestion, it's a thing
```
MARKDOWN
end
it
'returns a list of Gitlab::Diff::Suggestion'
do
expect
(
subject
).
to
all
(
be_a
(
Gitlab
::
Diff
::
Suggestion
))
expect
(
subject
.
size
).
to
eq
(
3
)
end
it
'suggestion with above and below param has correct data'
do
from_line
=
position
.
new_line
-
2
to_line
=
position
.
new_line
+
1
expect
(
subject
.
first
.
to_hash
).
to
include
(
from_content:
blob_lines_data
(
from_line
,
to_line
),
to_content:
" # above and below
\n
"
,
lines_above:
2
,
lines_below:
1
)
end
it
'suggestion with above param has correct data'
do
from_line
=
position
.
new_line
-
3
to_line
=
position
.
new_line
expect
(
subject
.
second
.
to_hash
).
to
eq
(
from_content:
blob_lines_data
(
from_line
,
to_line
),
to_content:
" # only above
\n
"
,
lines_above:
3
,
lines_below:
0
)
end
it
'suggestion with below param has correct data'
do
from_line
=
position
.
new_line
to_line
=
position
.
new_line
+
3
expect
(
subject
.
third
.
to_hash
).
to
eq
(
from_content:
blob_lines_data
(
from_line
,
to_line
),
to_content:
" # only below
\n
"
,
lines_above:
0
,
lines_below:
3
)
end
end
end
end
spec/models/suggestion_spec.rb
View file @
e540c0d7
...
...
@@ -21,6 +21,22 @@ describe Suggestion do
end
end
describe
'#diff_lines'
do
let
(
:suggestion
)
{
create
(
:suggestion
,
:content_from_repo
)
}
it
'returns parsed diff lines'
do
expected_diff_lines
=
Gitlab
::
Diff
::
SuggestionDiff
.
new
(
suggestion
).
diff_lines
diff_lines
=
suggestion
.
diff_lines
expect
(
diff_lines
.
size
).
to
eq
(
expected_diff_lines
.
size
)
expect
(
diff_lines
).
to
all
(
be_a
(
Gitlab
::
Diff
::
Line
))
expected_diff_lines
.
each_with_index
do
|
expected_line
,
index
|
expect
(
diff_lines
[
index
].
to_hash
).
to
eq
(
expected_line
.
to_hash
)
end
end
end
describe
'#appliable?'
do
context
'when note does not support suggestions'
do
it
'returns false'
do
...
...
spec/serializers/suggestion_entity_spec.rb
View file @
e540c0d7
...
...
@@ -13,8 +13,7 @@ describe SuggestionEntity do
subject
{
entity
.
as_json
}
it
'exposes correct attributes'
do
expect
(
subject
).
to
include
(
:id
,
:from_line
,
:to_line
,
:appliable
,
:applied
,
:from_content
,
:to_content
)
expect
(
subject
.
keys
).
to
match_array
([
:id
,
:appliable
,
:applied
,
:diff_lines
,
:current_user
])
end
it
'exposes current user abilities'
do
...
...
spec/services/preview_markdown_service_spec.rb
View file @
e540c0d7
...
...
@@ -2,7 +2,7 @@ require 'spec_helper'
describe
PreviewMarkdownService
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:project
)
{
create
(
:project
,
:repository
)
}
before
do
project
.
add_developer
(
user
)
...
...
@@ -20,23 +20,72 @@ describe PreviewMarkdownService do
end
describe
'suggestions'
do
let
(
:params
)
{
{
text:
"```suggestion
\n
foo
\n
```"
,
preview_suggestions:
preview_suggestions
}
}
let
(
:merge_request
)
do
create
(
:merge_request
,
target_project:
project
,
source_project:
project
)
end
let
(
:text
)
{
"```suggestion
\n
foo
\n
```"
}
let
(
:params
)
do
suggestion_params
.
merge
(
text:
text
,
target_type:
'MergeRequest'
,
target_id:
merge_request
.
iid
)
end
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
context
'when preview markdown param is present'
do
let
(
:preview_suggestions
)
{
true
}
let
(
:path
)
{
"files/ruby/popen.rb"
}
let
(
:line
)
{
10
}
let
(
:diff_refs
)
{
merge_request
.
diff_refs
}
it
'returns users referenced in text'
do
let
(
:suggestion_params
)
do
{
preview_suggestions:
true
,
file_path:
path
,
line:
line
,
base_sha:
diff_refs
.
base_sha
,
start_sha:
diff_refs
.
start_sha
,
head_sha:
diff_refs
.
head_sha
}
end
it
'returns suggestions referenced in text'
do
position
=
Gitlab
::
Diff
::
Position
.
new
(
new_path:
path
,
new_line:
line
,
diff_refs:
diff_refs
)
expect
(
Gitlab
::
Diff
::
SuggestionsParser
)
.
to
receive
(
:parse
)
.
with
(
text
,
position:
position
,
project:
merge_request
.
project
)
.
and_call_original
result
=
service
.
execute
expect
(
result
[
:suggestions
]).
to
all
(
be_a
(
Gitlab
::
Diff
::
Suggestion
))
end
context
'when user is not authorized'
do
let
(
:another_user
)
{
create
(
:user
)
}
let
(
:service
)
{
described_class
.
new
(
project
,
another_user
,
params
)
}
before
do
project
.
add_guest
(
another_user
)
end
it
'returns no suggestions'
do
result
=
service
.
execute
expect
(
result
[
:suggestions
]).
to
eq
([
'foo'
])
expect
(
result
[
:suggestions
]).
to
be_empty
end
end
end
context
'when preview markdown param is not present'
do
let
(
:preview_suggestions
)
{
false
}
let
(
:suggestion_params
)
do
{
preview_suggestions:
false
}
end
it
'returns
user
s referenced in text'
do
it
'returns
suggestion
s referenced in text'
do
result
=
service
.
execute
expect
(
result
[
:suggestions
]).
to
eq
([])
...
...
@@ -49,8 +98,8 @@ describe PreviewMarkdownService do
let
(
:params
)
do
{
text:
"Please do it
\n
/assign
#{
user
.
to_reference
}
"
,
quick_actions_
target_type:
'Issue'
,
quick_actions_
target_id:
issue
.
id
target_type:
'Issue'
,
target_id:
issue
.
id
}
end
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
...
...
@@ -72,7 +121,7 @@ describe PreviewMarkdownService do
let
(
:params
)
do
{
text:
"My work
\n
/estimate 2y"
,
quick_actions_
target_type:
'MergeRequest'
target_type:
'MergeRequest'
}
end
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
...
...
@@ -96,8 +145,8 @@ describe PreviewMarkdownService do
let
(
:params
)
do
{
text:
"My work
\n
/tag v1.2.3 Stable release"
,
quick_actions_
target_type:
'Commit'
,
quick_actions_
target_id:
commit
.
id
target_type:
'Commit'
,
target_id:
commit
.
id
}
end
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
...
...
spec/services/suggestions/apply_service_spec.rb
View file @
e540c0d7
...
...
@@ -51,6 +51,10 @@ describe Suggestions::ApplyService do
diff_refs:
merge_request
.
diff_refs
)
end
let
(
:diff_note
)
do
create
(
:diff_note_on_merge_request
,
noteable:
merge_request
,
position:
position
,
project:
project
)
end
let
(
:suggestion
)
do
create
(
:suggestion
,
:content_from_repo
,
note:
diff_note
,
to_content:
" raise RuntimeError, 'Explosion'
\n
# explosion?
\n
"
)
...
...
@@ -108,12 +112,6 @@ describe Suggestions::ApplyService do
target_project:
project
)
end
let!
(
:diff_note
)
do
create
(
:diff_note_on_merge_request
,
noteable:
merge_request
,
position:
position
,
project:
project
)
end
before
do
project
.
add_maintainer
(
user
)
end
...
...
@@ -192,11 +190,6 @@ describe Suggestions::ApplyService do
CONTENT
end
let
(
:merge_request
)
do
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
end
def
create_suggestion
(
diff
,
old_line:
nil
,
new_line:
nil
,
from_content
:,
to_content
:,
path
:)
position
=
Gitlab
::
Diff
::
Position
.
new
(
old_path:
path
,
new_path:
path
,
...
...
@@ -291,6 +284,55 @@ describe Suggestions::ApplyService do
expect
(
suggestion_2_diff
.
strip
).
to
eq
(
expected_suggestion_2_diff
.
strip
)
end
end
context
'multi-line suggestion'
do
let
(
:expected_content
)
do
<<~
CONTENT
require 'fileutils'
require 'open3'
module Popen
extend self
# multi
# line
vars = {
"PWD" => path
}
options = {
chdir: path
}
unless File.directory?(path)
FileUtils.mkdir_p(path)
end
@cmd_output = ""
@cmd_status = 0
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
@cmd_output << stdout.read
@cmd_output << stderr.read
@cmd_status = wait_thr.value.exitstatus
end
return @cmd_output, @cmd_status
end
end
CONTENT
end
let
(
:suggestion
)
do
create
(
:suggestion
,
:content_from_repo
,
note:
diff_note
,
lines_above:
2
,
lines_below:
3
,
to_content:
"# multi
\n
# line
\n
"
)
end
it_behaves_like
'successfully creates commit and updates suggestion'
end
end
context
'fork-project'
do
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment