Closed romanbsd closed 1 week ago
Capybara provides their implementation of have_ methods.
Which one do you call? Why you don’t call the Capybara’s one if they provide it? Does it suffer from the same issue?
If it’s a configuration issue and Capybara matchers were not included, can you please leave a note on what was the culprit for the future researchers?
I'll check with the team, thanks!
We actually have tests for passing keyword arguments through a have matcher which pass, so it must not be as simple as you say, but there is a possibility there are still keywork argument bugs around as most of RSpec predates keyword arguments. We can't simply add ** behaviour until we release RSpec 4 due to our supported Rubies.
Can you provide a pure Ruby test that fails?
Interestingly enough, I cannot reproduce it in a simple case. The capybara code (as I checked in debugger callstack) is from https://github.com/teamcapybara/capybara/blob/master/lib/capybara/session.rb#L774 and then https://github.com/teamcapybara/capybara/blob/master/lib/capybara/node/matchers.rb#L421 However, for this simple test case it passes:
class TestModel
class Internal
def has_field?(locator = nil, **options, &optional_filter_block)
true
end
end
def initialize
@internal = Internal.new
end
def has_field?(...)
@internal.has_field?(...)
end
end
RSpec.describe TestModel, type: :model do
let(:instance) { described_class.new }
it 'passes keyword arguments' do
expect(instance).to have_field('secret', with: 'something')
end
end
I'm puzzled.
I did some more investigation and at a guess, whats happening is that the capture of the arguments up in the DynamicPrediate
parent class is somehow being overriden, so that these hashes are not being marked as ruby2_keywords hashes, by default we use:
def initialize(method_name, *args, &block)
@method_name, @args, @block = method_name, args, block
end
ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
Which captures the keywords as the hash but marks them as ruby2 style which allows the splat to work.
So we're going to need some more information from you as to what else you have that might be monkey patching over the top, can you check the source location of RSpec::Matchers::BuiltIn::DynamicMatcher#initialize
in your broken suite?
I'm having hard time finding the source location:
(byebug) RSpec::Matchers::BuiltIn::DynamicPredicate.method(:initialize).source_location
nil
(byebug) RSpec::Matchers::BuiltIn::DynamicPredicate.method(:new).source_location
nil
RSpec::Matchers::BuiltIn::DynamicPredicate.instance_method(:initialize).source_location
.rvm/gems/ruby-3.2.4/gems/rspec-expectations-3.13.1/lib/rspec/matchers/built_in/has.rb", 10
In your monkey patch, what is the value of Hash.ruby2_keywords_hash(last_hash)
it's for example:
{:with=>"Beta experiment"}
I think what is interesting is the result of the predicate method Hash.ruby2_keywords_hash?(last_hash) which would be true or false.
What tools do you use for debugging, @romanbsd ?
Sorry yes thats what I meant
I use byebug, but can try anything else.
Can you please check what Hash.ruby2_keywords_hash?(last_hash) returns?
(byebug) Hash.ruby2_keywords_hash?(last_hash)
false
Ok I think this is likely something else interferring with the ruby2 keyword hash stuff then, in our vanilla implementation it returns true and everything works as expected. I'd recommend trying to eliminate things from your stack (temporarily) until you find what gem / code is interfering with this.
I'm closing this for now but if you can come up with a reproduction please re-open.
Any idea what it could be, or what code patterns should I search for?
You could try searching your code/dependencies for things monkey patching has's initializer,
The Has initializer is not monkey patched as far as I can tell. Is there some mechanism of ruby2_keywords that can be in effect here? How some external library can have an effect on this code?
Anything which intefers with the vanilla behaviour of the ruby2_keywords flag or our class and its methods could be at fault, you should also search for anything thats reopening the class (including er including itself into the class form example).
As I mentioned eliminating one gem at a time might be the best way to debug this
Subject of the issue
When working with capybara, the following fails:
Your environment
Steps to reproduce
Using capybara do
expect(page).to have_field('object_title', with: 'some name')
Expected behavior
It delegates to
has_field?('object_title', with: 'some name')
Actual behavior
It calls
has_field?('object_title', { with: 'some name' })
I made a following monkey patch to workaround the problem: