From e540c0d71e00c4ce031b94cf11ec3de905e87da7 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Thu, 4 Apr 2019 13:08:34 +0000 Subject: [PATCH] Fixed test specs - added suggestions to mock data - fixed props to be not required --- .../diffs/components/diff_line_note_form.vue | 6 +- .../notes/components/note_form.vue | 44 ++++++- .../components/lib/utils/diff_utils.js | 20 ++++ .../vue_shared/components/markdown/field.vue | 5 +- .../vue_shared/components/markdown/header.vue | 2 +- .../components/markdown/suggestion_diff.vue | 42 ++----- .../markdown/suggestion_diff_row.vue | 32 +++++ .../components/markdown/suggestions.vue | 38 +----- app/controllers/concerns/preview_markdown.rb | 2 +- app/serializers/issue_entity.rb | 2 +- .../merge_request_widget_entity.rb | 2 +- app/serializers/suggestion_entity.rb | 2 + app/serializers/suggestion_serializer.rb | 9 ++ app/services/concerns/suggestible.rb | 7 ++ app/services/preview_markdown_service.rb | 28 +++-- .../form_elements/_description.html.haml | 2 +- app/views/shared/notes/_form.html.haml | 2 +- .../osw-support-multi-line-suggestions.yml | 5 + .../img/multi-line-suggestion-preview.png | Bin 0 -> 61692 bytes .../img/multi-line-suggestion-syntax.png | Bin 0 -> 29753 bytes doc/user/discussions/index.md | 18 +++ lib/banzai/suggestions_parser.rb | 16 --- spec/controllers/projects_controller_spec.rb | 10 ++ .../user_suggests_changes_on_diff_spec.rb | 111 ++++++++++++++++-- .../markdown/suggestion_diff_row_spec.js | 98 ++++++++++++++++ spec/javascripts/notes/mock_data.js | 6 +- .../components/markdown/header_spec.js | 2 +- .../markdown/suggestion_diff_spec.js | 66 +++++++---- .../components/markdown/suggestions_spec.js | 109 +++++++---------- spec/lib/banzai/suggestions_parser_spec.rb | 32 ----- spec/lib/gitlab/diff/suggestion_spec.rb | 87 ++++++++++++-- .../gitlab/diff/suggestions_parser_spec.rb | 61 ++++++++++ spec/models/suggestion_spec.rb | 16 +++ spec/serializers/suggestion_entity_spec.rb | 3 +- .../services/preview_markdown_service_spec.rb | 73 ++++++++++-- .../suggestions/apply_service_spec.rb | 64 ++++++++-- 36 files changed, 755 insertions(+), 267 deletions(-) create mode 100644 app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js create mode 100644 app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue create mode 100644 app/serializers/suggestion_serializer.rb create mode 100644 changelogs/unreleased/osw-support-multi-line-suggestions.yml create mode 100644 doc/user/discussions/img/multi-line-suggestion-preview.png create mode 100644 doc/user/discussions/img/multi-line-suggestion-syntax.png delete mode 100644 lib/banzai/suggestions_parser.rb create mode 100644 spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js delete mode 100644 spec/lib/banzai/suggestions_parser_spec.rb diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue index bb66ab36283..41670b45798 100644 --- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue +++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue @@ -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" diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 57d6b181bd7..471323bfc83 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -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.exact.esc="cancelHandler(true)" @input="onInput" > diff --git a/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js b/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js new file mode 100644 index 00000000000..d1aba99ac22 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js @@ -0,0 +1,20 @@ +/* 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)); +} diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index eccf73e227c..0f3b3568414 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -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() diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index cc6ecdb0395..a5a5b2ef415 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -38,7 +38,7 @@ export default { ].join('\n'); }, mdSuggestion() { - return ['```suggestion', `{text}`, '```'].join('\n'); + return ['```suggestion:-0+0', `{text}`, '```'].join('\n'); }, }, mounted() { diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue index a351ca62c94..2eb4ec12a4a 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue @@ -1,24 +1,14 @@ + + diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue index 177d78cb904..8d3705e1e4a 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue @@ -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 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 }) => { diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb index f72d25fc54c..2a9729b6ffd 100644 --- a/app/controllers/concerns/preview_markdown.rb +++ b/app/controllers/concerns/preview_markdown.rb @@ -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]) } } diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb index c3f7d4651fb..914ad628a99 100644 --- a/app/serializers/issue_entity.rb +++ b/app/serializers/issue_entity.rb @@ -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 diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index d673f8ae896..4831eb32c96 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -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| diff --git a/app/serializers/suggestion_entity.rb b/app/serializers/suggestion_entity.rb index 4d0d4da10be..2dd62e19e29 100644 --- a/app/serializers/suggestion_entity.rb +++ b/app/serializers/suggestion_entity.rb @@ -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) diff --git a/app/serializers/suggestion_serializer.rb b/app/serializers/suggestion_serializer.rb new file mode 100644 index 00000000000..010344f9fcd --- /dev/null +++ b/app/serializers/suggestion_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class SuggestionSerializer < BaseSerializer + entity SuggestionEntity + + def represent_diff(resource) + represent(resource, { only: [:diff_lines] }) + end +end diff --git a/app/services/concerns/suggestible.rb b/app/services/concerns/suggestible.rb index 0b9822b1909..0cba9bf1b8a 100644 --- a/app/services/concerns/suggestible.rb +++ b/app/services/concerns/suggestible.rb @@ -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 diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb index c1655c38095..7386530f45f 100644 --- a/app/services/preview_markdown_service.rb +++ b/app/services/preview_markdown_service.rb @@ -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 params[:preview_suggestions] + return [] unless preview_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 diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml index 25df2fe5cd6..b11cb8a3076 100644 --- a/app/views/shared/form_elements/_description.html.haml +++ b/app/views/shared/form_elements/_description.html.haml @@ -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) diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index 6a1eea85fde..d91bc6e57c9 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -1,7 +1,7 @@ - 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) diff --git a/changelogs/unreleased/osw-support-multi-line-suggestions.yml b/changelogs/unreleased/osw-support-multi-line-suggestions.yml new file mode 100644 index 00000000000..8c8206c3822 --- /dev/null +++ b/changelogs/unreleased/osw-support-multi-line-suggestions.yml @@ -0,0 +1,5 @@ +--- +title: Support multi-line suggestions +merge_request: 25211 +author: +type: added diff --git a/doc/user/discussions/img/multi-line-suggestion-preview.png b/doc/user/discussions/img/multi-line-suggestion-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..4288d0ba034b29fec5c2ebb03f905aa7cede7bd2 GIT binary patch literal 61692 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 literal 0 HcmV?d00001 diff --git a/doc/user/discussions/img/multi-line-suggestion-syntax.png b/doc/user/discussions/img/multi-line-suggestion-syntax.png new file mode 100644 index 0000000000000000000000000000000000000000..df0c99b84ef48aaa60b1f8dbdaa7afb574116efc GIT binary patch literal 29753 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 [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 diff --git a/lib/banzai/suggestions_parser.rb b/lib/banzai/suggestions_parser.rb deleted file mode 100644 index 0d7f751bfc1..00000000000 --- a/lib/banzai/suggestions_parser.rb +++ /dev/null @@ -1,16 +0,0 @@ -# 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 diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 356d606d5c5..56d38b9475e 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -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) } diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb index c19e299097e..1b5dd6945e0 100644 --- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -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 diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js new file mode 100644 index 00000000000..866d6eb05c6 --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js @@ -0,0 +1,98 @@ +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'); + }); + }); +}); diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 348743081eb..1df5cf9ef68 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -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 = [ diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index e733a95288e..d4be2451f0b 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -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', () => { diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js index f87c2a92f47..ea74cb9eb21 100644 --- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js @@ -1,21 +1,50 @@ 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.fromLine); + expect(parseInt(fromLine, 10)).toBe(lines[0].old_line); }); 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 newLineNumbers = vm.$el.querySelectorAll('.qa-new-diff-line-number'); + it('renders new lines', () => { + const newLinesElements = 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); }); }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js index 33be63a3a1e..b7de40b4831 100644 --- a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js @@ -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: ` +
+
-oldtest
+
-
Suggestion 1
+
+newtest
- -
-
Suggestion 2
-
`, 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); }); }); }); diff --git a/spec/lib/banzai/suggestions_parser_spec.rb b/spec/lib/banzai/suggestions_parser_spec.rb deleted file mode 100644 index 79658d710ce..00000000000 --- a/spec/lib/banzai/suggestions_parser_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -# 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 diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb index 71fd25df698..d7ca0e0a522 100644 --- a/spec/lib/gitlab/diff/suggestion_spec.rb +++ b/spec/lib/gitlab/diff/suggestion_spec.rb @@ -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,17 +133,19 @@ 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' - 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) } + 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 it_behaves_like 'correct suggestion raw content' end diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb index 1119ea04995..1f2af42f6e7 100644 --- a/spec/lib/gitlab/diff/suggestions_parser_spec.rb +++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb @@ -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 diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb index cafc725dddb..8d4e9070b19 100644 --- a/spec/models/suggestion_spec.rb +++ b/spec/models/suggestion_spec.rb @@ -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 diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb index d38fc2b132b..d282a7f9c7a 100644 --- a/spec/serializers/suggestion_entity_spec.rb +++ b/spec/serializers/suggestion_entity_spec.rb @@ -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 diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index 85515d548a7..a1d31464e07 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -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\nfoo\n```", preview_suggestions: preview_suggestions } } + let(:merge_request) do + create(:merge_request, target_project: project, source_project: project) + end + let(:text) { "```suggestion\nfoo\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 } + + 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 - it 'returns users referenced in text' do result = service.execute - expect(result[:suggestions]).to eq(['foo']) + 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 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 users referenced in text' do + it 'returns suggestions 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) } diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index 80b5dcac6c7..7732767137c 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -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 -- GitLab