# frozen_string_literal: true require 'spec_helper' RSpec.describe Gitlab::Suggestions::FileSuggestion do def create_suggestion(new_line, to_content, lines_above = 0, lines_below = 0) position = Gitlab::Diff::Position.new(old_path: file_path, new_path: file_path, old_line: nil, new_line: new_line, diff_refs: merge_request.diff_refs) diff_note = create(:diff_note_on_merge_request, noteable: merge_request, position: position, project: project) create(:suggestion, :content_from_repo, note: diff_note, lines_above: lines_above, lines_below: lines_below, to_content: to_content) end let_it_be(:user) { create(:user) } let_it_be(:file_path) { 'files/ruby/popen.rb' } let_it_be(:project) { create(:project, :repository) } let_it_be(:merge_request) do create(:merge_request, source_project: project, target_project: project) end let_it_be(:suggestion1) do create_suggestion(9, " *** SUGGESTION 1 ***\n") end let_it_be(:suggestion2) do create_suggestion(15, " *** SUGGESTION 2 ***\n") end let(:suggestions) { [suggestion1, suggestion2] } let(:file_suggestion) { described_class.new(file_path, suggestions) } describe '#line_conflict' do def stub_suggestions(line_index_spans) fake_suggestions = line_index_spans.map do |span| double("Suggestion", from_line_index: span[:from_line_index], to_line_index: span[:to_line_index]) end allow(file_suggestion).to(receive(:suggestions).and_return(fake_suggestions)) end context 'when line ranges do not overlap' do it 'return false' do stub_suggestions( [ { from_line_index: 0, to_line_index: 10 }, { from_line_index: 11, to_line_index: 20 } ] ) expect(file_suggestion.line_conflict?).to be(false) end end context 'when line ranges are identical' do it 'returns true' do stub_suggestions( [ { from_line_index: 0, to_line_index: 10 }, { from_line_index: 0, to_line_index: 10 } ] ) expect(file_suggestion.line_conflict?).to be(true) end end context 'when one range starts, and the other ends, on the same line' do it 'returns true' do stub_suggestions( [ { from_line_index: 0, to_line_index: 10 }, { from_line_index: 10, to_line_index: 20 } ] ) expect(file_suggestion.line_conflict?).to be(true) end end context 'when one line range contains the other' do it 'returns true' do stub_suggestions( [ { from_line_index: 0, to_line_index: 10 }, { from_line_index: 5, to_line_index: 7 } ] ) expect(file_suggestion.line_conflict?).to be(true) end end context 'when line ranges overlap' do it 'returns true' do stub_suggestions( [ { from_line_index: 0, to_line_index: 10 }, { from_line_index: 8, to_line_index: 15 } ] ) expect(file_suggestion.line_conflict?).to be(true) end end context 'when no suggestions have been added' do it 'returns false' do expect(file_suggestion.line_conflict?).to be(false) end end end describe '#new_content' do context 'with two suggestions' do let(:suggestions) { [suggestion1, suggestion2] } it 'returns a blob with the suggestions applied to it' do expected_content = <<-CONTENT.strip_heredoc require 'fileutils' require 'open3' module Popen extend self def popen(cmd, path=nil) unless cmd.is_a?(Array) *** SUGGESTION 1 *** end path ||= Dir.pwd vars = { *** SUGGESTION 2 *** } 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 expect(file_suggestion.new_content).to eq(expected_content) end end context 'when no suggestions have been added' do let(:suggestions) { [] } it 'returns an empty string' do expect(file_suggestion.new_content).to eq('') end end context 'with multiline suggestions' do let(:suggestions) { [multi_suggestion1, multi_suggestion2, multi_suggestion3] } context 'when the previous suggestion increases the line count' do let!(:multi_suggestion1) do create_suggestion(9, " *** SUGGESTION 1 ***\n *** SECOND LINE ***\n *** THIRD LINE ***\n") end let!(:multi_suggestion2) do create_suggestion(15, " *** SUGGESTION 2 ***\n *** SECOND LINE ***\n") end let!(:multi_suggestion3) do create_suggestion(19, " chdir: *** SUGGESTION 3 ***\n") end it 'returns a blob with the suggestions applied to it' do expected_content = <<-CONTENT.strip_heredoc require 'fileutils' require 'open3' module Popen extend self def popen(cmd, path=nil) unless cmd.is_a?(Array) *** SUGGESTION 1 *** *** SECOND LINE *** *** THIRD LINE *** end path ||= Dir.pwd vars = { *** SUGGESTION 2 *** *** SECOND LINE *** } options = { chdir: *** SUGGESTION 3 *** } 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 expect(file_suggestion.new_content).to eq(expected_content) end end context 'when the previous suggestion decreases and increases the line count' do let!(:multi_suggestion1) do create_suggestion(9, " *** SUGGESTION 1 ***\n", 1, 1) end let!(:multi_suggestion2) do create_suggestion(15, " *** SUGGESTION 2 ***\n *** SECOND LINE ***\n") end let!(:multi_suggestion3) do create_suggestion(19, " chdir: *** SUGGESTION 3 ***\n") end it 'returns a blob with the suggestions applied to it' do expected_content = <<-CONTENT.strip_heredoc require 'fileutils' require 'open3' module Popen extend self def popen(cmd, path=nil) *** SUGGESTION 1 *** path ||= Dir.pwd vars = { *** SUGGESTION 2 *** *** SECOND LINE *** } options = { chdir: *** SUGGESTION 3 *** } 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 expect(file_suggestion.new_content).to eq(expected_content) end end context 'when the previous suggestion replaces with the same number of lines' do let!(:multi_suggestion1) do create_suggestion(9, " *** SUGGESTION 1 ***\n *** SECOND LINE ***\n *** THIRD LINE ***\n", 1, 1) end let!(:multi_suggestion2) do create_suggestion(15, " *** SUGGESTION 2 ***\n") end let!(:multi_suggestion3) do create_suggestion(19, " chdir: *** SUGGESTION 3 ***\n") end it 'returns a blob with the suggestions applied to it' do expected_content = <<-CONTENT.strip_heredoc require 'fileutils' require 'open3' module Popen extend self def popen(cmd, path=nil) *** SUGGESTION 1 *** *** SECOND LINE *** *** THIRD LINE *** path ||= Dir.pwd vars = { *** SUGGESTION 2 *** } options = { chdir: *** SUGGESTION 3 *** } 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 expect(file_suggestion.new_content).to eq(expected_content) end end context 'when the previous suggestion replaces multiple lines and the suggestions were applied out of order' do let(:suggestions) { [multi_suggestion1, multi_suggestion3, multi_suggestion2] } let!(:multi_suggestion1) do create_suggestion(9, " *** SUGGESTION 1 ***\n *** SECOND LINE ***\n *** THIRD LINE ***\n", 1, 1) end let!(:multi_suggestion3) do create_suggestion(19, " *** SUGGESTION 3 ***\n", 1, 1) end let!(:multi_suggestion2) do create_suggestion(15, " *** SUGGESTION 2 ***\n", 1, 1) end it 'returns a blob with the suggestions applied to it' do expected_content = <<-CONTENT.strip_heredoc require 'fileutils' require 'open3' module Popen extend self def popen(cmd, path=nil) *** SUGGESTION 1 *** *** SECOND LINE *** *** THIRD LINE *** path ||= Dir.pwd *** SUGGESTION 2 *** *** SUGGESTION 3 *** 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 expect(file_suggestion.new_content).to eq(expected_content) end end end end end