From 568e05a73b1c4aac814f75da488d3d1ed7d847e9 Mon Sep 17 00:00:00 2001 From: Jason Hollingsworth Date: Wed, 27 Nov 2013 12:24:27 -0600 Subject: Enable multiline select for blobs. Holding shift will allow users to click and select multiple lines. The behavior is similar to GitHub. Complete with tests. --- app/assets/javascripts/blob.js.coffee | 70 +++++++++++++++--- features/project/source/multiselect_blob.feature | 86 ++++++++++++++++++++++ features/steps/project/project_multiselect_blob.rb | 58 +++++++++++++++ 3 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 features/project/source/multiselect_blob.feature create mode 100644 features/steps/project/project_multiselect_blob.rb diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee index 03e280f8976..99cb1cec911 100644 --- a/app/assets/javascripts/blob.js.coffee +++ b/app/assets/javascripts/blob.js.coffee @@ -1,24 +1,76 @@ class BlobView constructor: -> + # handle multi-line select + handleMultiSelect = (e) -> + [ first_line, last_line ] = parseSelectedLines() + [ line_number ] = parseSelectedLines($(this).attr("id")) + hash = "L#{line_number}" + + if e.shiftKey and not isNaN(first_line) and not isNaN(line_number) + if line_number < first_line + last_line = first_line + first_line = line_number + else + last_line = line_number + + hash = if first_line == last_line then "L#{first_line}" else "L#{first_line}-#{last_line}" + + setHash(hash) + e.preventDefault() + # See if there are lines selected # "#L12" and "#L34-56" supported - highlightBlobLines = -> - if window.location.hash isnt "" - matches = window.location.hash.match(/\#L(\d+)(\-(\d+))?/) + highlightBlobLines = (e) -> + [ first_line, last_line ] = parseSelectedLines() + + unless isNaN first_line + $("#tree-content-holder .highlight .line").removeClass("hll") + $("#LC#{line}").addClass("hll") for line in [first_line..last_line] + $("#L#{first_line}").ScrollTo() unless e? + + # parse selected lines from hash + # always return first and last line (initialized to NaN) + parseSelectedLines = (str) -> + first_line = NaN + last_line = NaN + hash = str || window.location.hash + + if hash isnt "" + matches = hash.match(/\#?L(\d+)(\-(\d+))?/) first_line = parseInt(matches?[1]) last_line = parseInt(matches?[3]) + last_line = first_line if isNaN(last_line) + + [ first_line, last_line ] + + setHash = (hash) -> + hash = hash.replace(/^\#/, "") + nodes = $("#" + hash) + # if any nodes are using this id, they must be temporarily changed + # also, add a temporary div at the top of the screen to prevent scrolling + if nodes.length > 0 + scroll_top = $(document).scrollTop() + nodes.attr("id", "") + tmp = $("
") + .css({ position: "absolute", visibility: "hidden", top: scroll_top + "px" }) + .attr("id", hash) + .appendTo(document.body) + + window.location.hash = hash + + # restore the nodes + if nodes.length > 0 + tmp.remove() + nodes.attr("id", hash) - unless isNaN first_line - last_line = first_line if isNaN(last_line) - $("#tree-content-holder .highlight .line").removeClass("hll") - $("#LC#{line}").addClass("hll") for line in [first_line..last_line] - $("#L#{first_line}").ScrollTo() + # initialize multi-line select + $("#tree-content-holder .line_numbers a[id^=L]").on("click", handleMultiSelect) # Highlight the correct lines on load highlightBlobLines() # Highlight the correct lines when the hash part of the URL changes - $(window).on 'hashchange', highlightBlobLines + $(window).on("hashchange", highlightBlobLines) @BlobView = BlobView diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature new file mode 100644 index 00000000000..3038c0814ad --- /dev/null +++ b/features/project/source/multiselect_blob.feature @@ -0,0 +1,86 @@ +Feature: Project Multiselect Blob + Background: + Given I sign in as a user + And I own project "Shop" + And I visit project source page + And I click on "Gemfile.lock" file in repo + + @javascript + Scenario: I click line 1 in file + When I click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + + @javascript + Scenario: I shift-click line 1 in file + When I shift-click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + + @javascript + Scenario: I click line 1 then click line 2 in file + When I click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + Then I click line 2 in file + Then I should see "L2" as URI fragment + And I should see line 2 highlighted + + @javascript + Scenario: I click various line numbers to test multiselect + Then I click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + Then I shift-click line 2 in file + Then I should see "L1-2" as URI fragment + And I should see lines 1-2 highlighted + Then I shift-click line 3 in file + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I click line 3 in file + Then I should see "L3" as URI fragment + And I should see line 3 highlighted + Then I shift-click line 1 in file + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I shift-click line 5 in file + Then I should see "L1-5" as URI fragment + And I should see lines 1-5 highlighted + Then I shift-click line 4 in file + Then I should see "L1-4" as URI fragment + And I should see lines 1-4 highlighted + Then I click line 5 in file + Then I should see "L5" as URI fragment + And I should see line 5 highlighted + Then I shift-click line 3 in file + Then I should see "L3-5" as URI fragment + And I should see lines 3-5 highlighted + Then I shift-click line 1 in file + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I shift-click line 1 in file + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + + @javascript + Scenario: I multiselect lines 1-5 and then go back and forward in history + When I click line 1 in file + And I shift-click line 3 in file + And I shift-click line 2 in file + And I shift-click line 5 in file + Then I should see "L1-5" as URI fragment + And I should see lines 1-5 highlighted + Then I go back in history + Then I should see "L1-2" as URI fragment + And I should see lines 1-2 highlighted + Then I go back in history + Then I should see "L1-3" as URI fragment + And I should see lines 1-3 highlighted + Then I go back in history + Then I should see "L1" as URI fragment + And I should see line 1 highlighted + Then I go forward in history + And I go forward in history + And I go forward in history + Then I should see "L1-5" as URI fragment + And I should see lines 1-5 highlighted \ No newline at end of file diff --git a/features/steps/project/project_multiselect_blob.rb b/features/steps/project/project_multiselect_blob.rb new file mode 100644 index 00000000000..3d330e837c1 --- /dev/null +++ b/features/steps/project/project_multiselect_blob.rb @@ -0,0 +1,58 @@ +class ProjectMultiselectBlob < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + class << self + def click_line_steps(*line_numbers) + line_numbers.each do |line_number| + step "I click line #{line_number} in file" do + find("#L#{line_number}").click + end + + step "I shift-click line #{line_number} in file" do + script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));" + page.evaluate_script(script) + end + end + end + + def check_state_steps(*ranges) + ranges.each do |range| + fragment = range.kind_of?(Array) ? "L#{range.first}-#{range.last}" : "L#{range}" + pluralization = range.kind_of?(Array) ? "s" : "" + + step "I should see \"#{fragment}\" as URI fragment" do + URI.parse(current_url).fragment.should == fragment + end + + step "I should see line#{pluralization} #{fragment[1..-1]} highlighted" do + ids = Array(range).map { |n| "LC#{n}" } + extra = false + + highlighted = all("#tree-content-holder .highlight .line.hll") + highlighted.each do |element| + extra ||= ids.delete(element[:id]).nil? + end + + extra.should be_false and ids.should be_empty + end + end + end + end + + click_line_steps *Array(1..5) + check_state_steps *Array(1..5), Array(1..2), Array(1..3), Array(1..4), Array(1..5), Array(3..5) + + step 'I go back in history' do + page.evaluate_script("window.history.back()") + end + + step 'I go forward in history' do + page.evaluate_script("window.history.forward()") + end + + step 'I click on "Gemfile.lock" file in repo' do + click_link "Gemfile.lock" + end +end \ No newline at end of file -- cgit v1.2.1