diff --git a/app/helpers/searches_helper.rb b/app/helpers/searches_helper.rb index f3b22e69..72d9966d 100644 --- a/app/helpers/searches_helper.rb +++ b/app/helpers/searches_helper.rb @@ -22,7 +22,10 @@ def highlight_searched_content(leaf, content, query) end private + # Terms come from FTS5 highlight() spans, which can include document + # punctuation (e.g. a phrase match spanning "foo) bar"). Escape them so + # metacharacters are matched literally instead of raising a RegexpError. def whole_word_matchers(terms) - terms.map { |term| /\b#{term}\b/ } + terms.map { |term| /\b#{Regexp.escape(term)}\b/ } end end diff --git a/test/controllers/leafables_controller_test.rb b/test/controllers/leafables_controller_test.rb index d9e383c7..0d180e57 100644 --- a/test/controllers/leafables_controller_test.rb +++ b/test/controllers/leafables_controller_test.rb @@ -30,6 +30,20 @@ class LeafablesControllerTest < ActionDispatch::IntegrationTest assert_select "mark", "great" end + test "show highlights a phrase match whose span contains regex metacharacters" do + sign_out + books(:handbook).update!(published: true) + + section = Section.new(body: "alpha) omega in the body") + books(:handbook).press(section, title: "Punctuated") + section.leaf.reindex + + get leafable_slug_path(section.leaf), params: { search: '"alpha omega"' } + + assert_response :success + assert_select "mark", text: /alpha\) omega/ + end + test "show does not allow public access to an unpublished book" do sign_out diff --git a/test/helpers/searches_helper_test.rb b/test/helpers/searches_helper_test.rb index 7b144406..a71c9571 100644 --- a/test/helpers/searches_helper_test.rb +++ b/test/helpers/searches_helper_test.rb @@ -1,6 +1,7 @@ require "test_helper" class SearchesHelperTest < ActionView::TestCase + include PagesHelper test "sanitize_search_result preserves mark tags" do assert_equal "findme text", sanitize_search_result("findme text") end @@ -16,4 +17,14 @@ class SearchesHelperTest < ActionView::TestCase test "sanitize_search_result strips attributes from mark tags" do assert_equal "findme text", sanitize_search_result(' text') end + + test "highlight_searched_content handles matched terms containing regex metacharacters" do + leaf = Struct.new(:terms) do + def matches_for_highlight(_query) = terms + end.new([ "foo) bar" ]) + + result = highlight_searched_content(leaf, "foo) bar in the body", "foo bar") + + assert_includes result, "foo) bar" + end end