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

Fix an incorrect autocorrect for Capybara/RedundantWithinFind when escape required css selector #137

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ydah
Copy link
Member

@ydah ydah commented Oct 31, 2024

Fix: #136

Previously, the following automatic corrections were made.

before:

within find_by_id("array-form-session.dates") do
  expect(page).to have_text(:visible, "YYYY/MM/DD")
end

after:

within "#array-form-session.dates" do
  expect(page).to have_text(:visible, "YYYY/MM/DD")
end

This is . in find_by_id has the same meaning as the escaped id. In other words, when replacing within, the . must be escaped.

This PR has been modified so that escaping is correctly done in selectors that require escaping. This will prevent the behavior from changing before and after the automatic correction.


Before submitting the PR make sure the following are checked:

  • Feature branch is up-to-date with main (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • [-] Updated documentation.
  • Added an entry to the CHANGELOG.md if the new code introduces user-observable changes.
  • The build (bundle exec rake) passes (be sure to run this locally, since it may produce updated documentation that you will need to commit).

…escape required css selector

Fix: #136

Previously, the following automatic corrections were made.

before:
```ruby
within find_by_id("array-form-session.dates") do
  expect(page).to have_text(:visible, "YYYY/MM/DD")
end
```

after:
```ruby
within "#array-form-session.dates" do
  expect(page).to have_text(:visible, "YYYY/MM/DD")
end
```

This is `.' in find_by_id. ` has the same meaning as the escaped id.
In other words, when replacing within, the `. ` must be escaped.

This PR has been modified so that escaping is correctly done in selectors that require escaping.
This will prevent the behavior from changing before and after the automatic correction.
@ydah ydah requested a review from a team as a code owner October 31, 2024 16:18
@ydah
Copy link
Member Author

ydah commented Dec 14, 2024

@hatsu38 Does this change solve your issue?

@hatsu38
Copy link

hatsu38 commented Dec 16, 2024

@ydah
Thank you for addressing this issue.
I tested this branch by running bundle install.

It seems that RuboCop now properly escapes the ..

within find_by_id("array-form-session.dates") do ~ end

-> match <div id="array-form-session.dates">

within "array-form-session\.dates" do ~ end

-> match <div id="array-form-session" class="dates">

both within "#array-form-session.dates" and within "#array-form-session\.dates" appear to be looking for <div id="array-form-session" class="dates">.

So, As mentioned in the ISSUE, the behavior changes as a result of running rubocop -A.

Therefore, I believe my issue has not yet been resolved.

@pirj
Copy link
Member

pirj commented Jan 9, 2025

For the context: https://stackoverflow.com/questions/12310090/css-selector-with-period-in-id

@hatsu38 does the second method, [id='some.id'] work for you?

I suggest opening an issue against capybara, as if the backslash quote doesn’t work, it’s in their code.
Unless there’s something that makes it inherently impossible to make within work with backslash quoting on Capybara side, I suggest continuing as is, and wait for Capybara to fix within to support that.

if node.method?(:find_by_id)
replaced.sub(/\A(["'])/, '\1#')
node.first_argument.source.match(/\A(['"])(.*)['"]\z/) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won’t work with any other form of string, like triple-quoted, those weird %{} etc ones, won’t work with multi-line?
I suggest creating an issue to keep track of this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use something like string_value, does rubocop-ast provide this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️This also has a risk of introducing an incorrect match for strings that contain the other quote inside them and being multi-line:

"[id='
boom']"

Doesn’t it?

Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!
I’m a bit concerned that this change goes above the minimum required to fix the issue, and this involves risks of side effects.

if node.method?(:find_by_id)
replaced.sub(/\A(["'])/, '\1#')
node.first_argument.source.match(/\A(['"])(.*)['"]\z/) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use something like string_value, does rubocop-ast provide this?

end

context 'when selector contains NULL character' do
let(:selector) { "abc\0def" }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this realistic?

context 'when selector is a single hyphen' do
let(:selector) { '-' }

it 'escapes the hyphen character' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Does it have a special meaning as a CSS selector?

context 'when selector contains control characters' do
let(:selector) { "abc\x01\x1F\x7Fdef" }

it 'escapes control characters as hexadecimal with a trailing space' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to handle this?

end
end

context 'when selector starts with a digit' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this possible? Why would anyone do this?

end
end

context 'when selector starts with a hyphen followed by a digit' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_); they cannot start with a digit, two hyphens, or a hyphen followed by a digit.

This seems to e an invalid CSS selector case, why should we handle it?

if node.method?(:find_by_id)
replaced.sub(/\A(["'])/, '\1#')
node.first_argument.source.match(/\A(['"])(.*)['"]\z/) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️This also has a risk of introducing an incorrect match for strings that contain the other quote inside them and being multi-line:

"[id='
boom']"

Doesn’t it?

# css_escape('some-id.with-priod') # => some-id\.with-priod
# @reference
# https://github.com/mathiasbynens/CSS.escape/blob/master/css.escape.js
def css_escape(selector) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you feel to only add handling for the period in this PR, and leaving the rest for later?

@hatsu38
Copy link

hatsu38 commented Jan 11, 2025

@pirj @ydah

Thanks for your comment!

I tried it again. Originally, I was using within find_by_id("enum-session.session_template_id"),
but within "#enum-session\.session_template_id" didn’t work.
However, your suggestion to use within "[id='enum-session.session_template_id']" did work!

And, I found on Stack Overflow that within "#enum-session\\.session_template_id" also works.

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

Successfully merging this pull request may close these issues.

Unsafe Auto-correction by Capybara/RedundantWithinFind Can Lead to Incorrect Behavior
3 participants