diff --git a/lib/rspec/support/method_signature_verifier.rb b/lib/rspec/support/method_signature_verifier.rb index f3524303..dd6bb971 100644 --- a/lib/rspec/support/method_signature_verifier.rb +++ b/lib/rspec/support/method_signature_verifier.rb @@ -84,7 +84,7 @@ def invalid_kw_args_from(given_kw_args) def has_kw_args_in?(args) Hash === args.last && could_contain_kw_args?(args) && - (args.last.empty? || args.last.keys.any? { |x| x.is_a?(Symbol) }) + (args.last.empty? || args.last.keys.any? { |x| x.is_a?(Symbol) || x.is_a?(String) }) end # Without considering what the last arg is, could it @@ -365,12 +365,12 @@ def unlimited_args? def split_args(*args) kw_args = if @signature.has_kw_args_in?(args) last = args.pop - non_kw_args = last.reject { |k, _| k.is_a?(Symbol) } + non_kw_args = last.reject { |k, _| k.is_a?(Symbol) || k.is_a?(String) } if non_kw_args.empty? last.keys else args << non_kw_args - last.select { |k, _| k.is_a?(Symbol) }.keys + last.select { |k, _| k.is_a?(Symbol) || k.is_a(String) }.keys end else [] diff --git a/spec/rspec/support/method_signature_verifier_spec.rb b/spec/rspec/support/method_signature_verifier_spec.rb index d342425a..e995f73a 100644 --- a/spec/rspec/support/method_signature_verifier_spec.rb +++ b/spec/rspec/support/method_signature_verifier_spec.rb @@ -384,17 +384,21 @@ def arity_kw(x, y = {}, z:2); end expect(valid?(nil, :a => 1)).to eq(false) end - it 'treats symbols as keyword arguments and the rest as optional argument' do - expect(valid?(nil, 'a' => 1)).to eq(true) - expect(valid?(nil, 'a' => 1, :z => 3)).to eq(true) - expect(valid?(nil, 'a' => 1, :b => 3)).to eq(false) - expect(valid?(nil, 'a' => 1, :b => 2, :z => 3)).to eq(false) + it 'treats symbols as keyword arguments' do + expect(valid?(nil, :z => 3)).to eq(true) + expect(valid?(nil, :b => 3)).to eq(false) + expect(valid?(nil, :b => 2, :z => 3)).to eq(false) + end + + it 'treats string keys as invalid keyword arguments' do + expect(valid?(nil, 'a' => 1)).to eq(false) + expect(valid?(nil, 'a' => 1, :z => 3)).to eq(false) end it 'mentions the invalid keyword args in the error', :pending => RSpec::Support::Ruby.jruby? && !RSpec::Support::Ruby.jruby_9000? do expect(error_for(1, 2, :a => 0)).to eq("Invalid keyword arguments provided: a") expect(error_for(1, :a => 0)).to eq("Invalid keyword arguments provided: a") - expect(error_for(1, 'a' => 0, :b => 0)).to eq("Invalid keyword arguments provided: b") + expect(error_for(1, 'a' => 0, :b => 0)).to eq("Invalid keyword arguments provided: a, b") end it 'describes invalid arity precisely' do @@ -656,6 +660,41 @@ def arity_required_kw_splat(w, *x, y:, z:, a: 'default'); end end end + describe 'a method with only a keyword arg splat' do + eval <<-RUBY + def arity_kw_arg_splat(**rest); end + RUBY + + let(:test_method) { method(:arity_kw_arg_splat) } + + it 'allows undeclared keyword args' do + expect(valid?(:x => 1)).to eq(true) + expect(valid?(:x => 1, 'y' => 2)).to eq(true) + end + + it 'mentions the required kw args and keyword splat in the description' do + expect(signature_description).to \ + eq("any additional keyword args") + end + + describe 'with an expectation object' do + it 'allows zero non-kw args' do + expect(validate_expectation 0).to eq(true) + expect(validate_expectation 1).to eq(false) + expect(validate_expectation 0, 0).to eq(true) + expect(validate_expectation 0, 1).to eq(false) + end + + it 'does not match unlimited arguments' do + expect(validate_expectation :unlimited_args).to eq(false) + end + + it 'matches arbitrary keywords' do + expect(validate_expectation :arbitrary_kw_args).to eq(true) + end + end + end + describe 'a method with required keyword arguments and a keyword arg splat' do eval <<-RUBY def arity_kw_arg_splat(x:, **rest); end @@ -666,6 +705,7 @@ def arity_kw_arg_splat(x:, **rest); end it 'allows extra undeclared keyword args' do expect(valid?(:x => 1)).to eq(true) expect(valid?(:x => 1, :y => 2)).to eq(true) + expect(valid?(:x => 1, :y => 2, 'z' => 3)).to eq(true) end it 'mentions missing required keyword args in the error' do @@ -730,7 +770,9 @@ def arity_kw_arg_splat(x, **rest); end it 'allows a single arg and any number of keyword args' do expect(valid?(nil)).to eq(true) expect(valid?(nil, :x => 1)).to eq(true) + expect(valid?(nil, 'x' => 1)).to eq(true) expect(valid?(nil, :x => 1, :y => 2)).to eq(true) + expect(valid?(nil, :x => 1, :y => 2, 'z' => 3)).to eq(true) expect(valid?(:x => 1)).to eq(true) expect(valid?(nil, {})).to eq(true)