Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SubstitutionContext#substitute! fails with multiple patterns #75

Closed
speckins opened this issue Jun 27, 2018 · 1 comment · Fixed by #76
Closed

SubstitutionContext#substitute! fails with multiple patterns #75

speckins opened this issue Jun 27, 2018 · 1 comment · Fixed by #76

Comments

@speckins
Copy link
Contributor

speckins commented Jun 27, 2018

When passing multiple patterns to #assert_select, an invalid selector is built because of the question mark (?) included in the substitution, "(?-mix:pattern)".

Here's a test case. I ran it from the test/ directory of this repo with Ruby 2.5.1.

# test/bug_test.rb
require 'test_helper'
require 'rails/dom/testing/assertions/selector_assertions'

class BugTest < ActiveSupport::TestCase
  include Rails::Dom::Testing::Assertions::SelectorAssertions

  def document_root_element
    @html_document.root
  end

  def render content
    @html_document = Nokogiri::HTML::Document.parse content
  end

  # Passes
  def test_substituted_selector
    render '<input name="item-10" value="1">'
    assert_select ':match("name", "(?-mix:item-\\d+)"):match("value", "(?-mix:\\d+)")'
  end

  # Fails with:
  #   Nokogiri::CSS::SyntaxError: unexpected '(' after '["\"name\"", "\"(\""]'
  def test_multiple_patterns
    render '<input name="item-10" value="1">'
    assert_select ':match("name", ?):match("value", ?)', /item-\d+/, /\d+/
  end
end

The problem is in SubstitutionContext#substitute! when format_for_presentation is false. On the first iteration, the first question mark is replaced with a string that also contains a question mark:

"input[name=\"(?-mix:item-\\d+)\"][value=?]"

On the second iteration, instead of replacing the next question mark, the one from the replacement is itself replaced:

"input[name=\"(\"(?-mix:\\d+)\"-mix:item-\\d+)\"][value=?]"
@speckins speckins changed the title SubstitutionContext#substitute fails with multiple patterns SubstitutionContext#substitute! fails with multiple patterns Jun 27, 2018
@speckins
Copy link
Contributor Author

Using String#gsub would fix the "replacing the replacement" problem, but the block form behaves differently than the replacement-as-argument form and requires an additional #sub to "unescape" the backslashes.

def substitute!(selector, values, format_for_presentation = false)
  selector.gsub @substitute do |match|
    next match[0] if values.empty? || !substitutable?(values.first)
    matcher_for(values.shift, format_for_presentation).sub '\\\\', '\\'
  end
end

speckins added a commit to speckins/rails-dom-testing that referenced this issue Jun 27, 2018
Since SubstitutionContext#substitute! uses while/#sub! to replace '?' in
the selector, and the replacement text contains that character, e.g.,
"(?-mix:\\d+)", if the selector requires more than one replacement, the
replaced text is matched by the next iteration.

	# given this assertion
	assert_select "input[name=?][value=?]", /item-\d+/, /\d+/

	# first iteration
	"input[name=\"(?-mix:item-\\d+)\"][value=?]"

	# second iteration
	"input[name=\"(\"(?-mix:\\d+)\"-mix:item-\\d+)\"][value=?]"

This commit uses #gsub to avoid replacing already substituted text.

Fixes rails#75.
rafaelfranca pushed a commit that referenced this issue Mar 7, 2022
Since SubstitutionContext#substitute! uses while/#sub! to replace '?' in
the selector, and the replacement text contains that character, e.g.,
"(?-mix:\\d+)", if the selector requires more than one replacement, the
replaced text is matched by the next iteration.

	# given this assertion
	assert_select "input[name=?][value=?]", /item-\d+/, /\d+/

	# first iteration
	"input[name=\"(?-mix:item-\\d+)\"][value=?]"

	# second iteration
	"input[name=\"(\"(?-mix:\\d+)\"-mix:item-\\d+)\"][value=?]"

This commit uses #gsub to avoid replacing already substituted text.

Fixes #75.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

1 participant