rspec-sleeping_king_studios 2.4.0 → 2.6.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12315df4c6674ce1c031e3028a13fca1950cbb5590bfa7d1bce275f73b7636df
4
- data.tar.gz: 88d768a0f44190f0b338385c70a37601fc9358f1ed2150b1c7ce09a3df03042e
3
+ metadata.gz: 8dd0e24014dae4f35a224e0c8d244b7e20d827d22c88ddf966f7ae201b1e148e
4
+ data.tar.gz: c3658bf3b7261942c9e166df38b7fdf5639688a074ca3a4cfb70992ae2698c8e
5
5
  SHA512:
6
- metadata.gz: 411868e2e3e57f709154152b0669cfdfdaee04512571ebe5b5342d14f78f0b8d825a8ebbf2bceae9ab3c788bbd1a4b1d4c28950864e14d15387bc2b6b364cfd9
7
- data.tar.gz: e839d7c324b6bd127a6c59b267d0941e21965b81a3bdec559468cbcf621ff1236e776fa42e55eaee633f44d6784e317645ad7eece50ff6eabfdfee3ee03083f3
6
+ metadata.gz: 8fb97318df347582c4204f6a7f61e359d87069952f24ef72e928514e97d84fd82372d77300c7e1f01669fea239526ea6021d28be151f49177939ad0760755667
7
+ data.tar.gz: 2e1da21ffdd79e55c5891dd3a7f10247bb5aed761454ee1f7898275788cc77022ac3d0deefe3ac4a7470b0696d0ee6accb94809794b72f19a410bcd16dc4f787
data/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.6.0
4
+
5
+ Deprecated the DelegateMethod matcher. Use `rspec-mocks` expectations instead.
6
+
7
+ Deprecated the Include matcher with a block. Use the `satisfy` matcher instead.
8
+
9
+ ### Matchers
10
+
11
+ Updated RespondToMatcher to check the arity of `#initialize` when matching against a class and the method name is `:new`.
12
+
13
+ ## 2.5.1
14
+
15
+ Fixed a compatibility issue with Rails 6 configuration objects.
16
+
17
+ ## 2.5.0
18
+
19
+ Added support for Ruby 2.6, dropped support for Ruby 2.3.
20
+
21
+ ### Matchers
22
+
23
+ Implemented the BeAUuidMatcher, which expects the value to be a UUID string.
24
+
25
+ Implemented the DeepMatcher, which recursively compares data structures.
26
+
27
+ expect(response).to deep_match(
28
+ status: 200,
29
+ body: {
30
+ order: {
31
+ id: an_instance_of(Integer),
32
+ total: '9.99'
33
+ }
34
+ }
35
+ )
36
+
37
+ Improved the HaveChangedMatcher to check the before and after `hash` value. This detects when an object is replaced by different but equal object, or when the initial object is a nested data structure (e.g. an Array of Hashes) and the internal elements are modified.
38
+
39
+ Improved failure message of the HaveChangedMatcher when given a receiver and message.
40
+
41
+ ## 2.4.1
42
+
43
+ Added support for RSpec 3.8.
44
+
3
45
  ## 2.4.0
4
46
 
5
47
  Dropped support for RSpec 3.3.
data/DEVELOPMENT.md CHANGED
@@ -1,26 +1,13 @@
1
1
  # Development Notes
2
2
 
3
- ## Version 2.4
3
+ ## Version 2.6
4
4
 
5
- ### Bug Fixes
6
-
7
- - when skipping a shared example group with xinclude_examples, the generated context is named '(focused)'. It should instead be named '(skipped)'.
8
-
9
- ### Features
10
-
11
- - Alias `have_reader`, etc as `define_reader`.
12
- - Also alias shared examples.
13
- - Alias `have_constant` as `define_constant`.
14
- - Alias #immutable as #frozen.
15
- - Also alias shared examples.
16
-
17
- ### Maintenance
18
-
19
- - Refactor all macro specs from spec/rspec/sleeping_king_studios/matchers/macros to the directories corresponding to the source files.
5
+ - Deprecate `delegate_method` matcher, `include` matcher with block.
20
6
 
21
7
  ## Version 3.0
22
8
 
23
9
  - Extract out Rails-specific matchers to RSpec::SleepingKingStudios::Rails.
10
+ - Drop Rails 3 support.
24
11
  - Refactor property, constant matchers to Define$1Matcher.
25
12
  - HaveConstantMatcher, HaveReaderMatcher, HavePredicateMatcher, HavePropertyMatcher, HaveWriterMatcher.
26
13
  - Designate define_* macros as primary, have_* as aliases.
@@ -31,6 +18,12 @@
31
18
 
32
19
  ## Future Tasks
33
20
 
21
+ - DeepMatcher: |
22
+ - indifferent - symbol/string keys
23
+ - ordered - pre-sort arrays?
24
+ - only homogenous arrays?
25
+ - odd results unless equality comparison
26
+
34
27
  ### Bug Fixes
35
28
 
36
29
  - false negative on #alias_method?
@@ -58,8 +51,6 @@
58
51
 
59
52
  - BeImmutableMatcher (NEW):
60
53
  - Implement be_immutable matcher.
61
- - HaveChangedMatcher (NEW):
62
- - Add spy+matcher for expect(my_object, :my_method).to have_changed ?
63
54
  - RespondToMatcher:
64
55
  - Implement RespondToMatcher#with_optional_keywords, #with_required_keywords.
65
56
  - Implement RespondToMatcher#with_at_least(N).arguments, equivalent to with(N).arguments.and_unlimited_arguments.
data/README.md CHANGED
@@ -8,9 +8,9 @@ RSpec::SleepingKingStudios is tested against RSpec 3.3 through 3.7.
8
8
 
9
9
  Currently, the following versions of Ruby are officially supported:
10
10
 
11
- * 2.3
12
11
  * 2.4
13
12
  * 2.5
13
+ * 2.6
14
14
 
15
15
  For Ruby 2.0 support, use version 2.1 or earlier: `gem "rspec-sleeping_king_studios", "~> 2.1.1"`.
16
16
 
@@ -322,7 +322,166 @@ A simplified syntax for re-using shared context or examples without having to ex
322
322
 
323
323
  (also `::xwrap_context`) A shortcut for wrapping the context or examples in an automatically-skipped `describe` block, similar to the built-in `xit` and `xdescribe` methods.
324
324
 
325
- ## Custom Matchers
325
+ ## Contracts
326
+
327
+ ```ruby
328
+ require 'rspec/sleepingkingstudios/contract'
329
+ ```
330
+
331
+ A Contract encapsulates a set of RSpec expectations, which can then be used when defining a spec.
332
+
333
+ ```ruby
334
+ module GreetContract
335
+ extend RSpec::SleepingKingStudios::Contract
336
+
337
+ describe '#greet' do
338
+ it { expect(subject).to respond_to(:greet).with(1).argument }
339
+
340
+ it { expect(subject.greet 'programs').to be == 'Greetings, programs!' }
341
+ end
342
+ end
343
+
344
+ RSpec.describe Greeter do
345
+ include GreetContract
346
+ end
347
+ ```
348
+
349
+ Using a contract allows for examples to be shared between different specs, or even between projects.
350
+
351
+ ### Contract Methods
352
+
353
+ Not all RSpec methods are defined in a Contract. Only methods that define an example (`it`) or an example group (`context` or `describe`) can be used at the top level of a Contract. However, all RSpec methods (including methods that modify the current scope, such as `let` and the `before`/`around`/`after` filters) can be used inside an example group as normal.
354
+
355
+ #### `::context`
356
+
357
+ Defines an example group inside the contract. This example group will be defined on all specs that include the contract.
358
+
359
+ ```ruby
360
+ module TransformationContract
361
+ extend RSpec::SleepingKingStudios::Contract
362
+
363
+ context 'when the moon is full' do
364
+ let(:moon_phase) { :full }
365
+
366
+ it { expect(werewolf).to be_transformed }
367
+ end
368
+ end
369
+ ```
370
+
371
+ #### `::describe`
372
+
373
+ Defines an example group inside the contract. This example group will be defined on all specs that include the contract.
374
+
375
+ ```ruby
376
+ module SilverContract
377
+ extend RSpec::SleepingKingStudios::Contract
378
+
379
+ describe 'with a silver weapon' do
380
+ before(:example) do
381
+ weapon.material = 'silver'
382
+ end
383
+
384
+ it 'should kill the werewolf' do
385
+ expect(attack(werewolf, weapon)).to change(werewolf, :alive?).to be false
386
+ end
387
+ end
388
+ end
389
+ ```
390
+
391
+ #### `::it`
392
+
393
+ Defines an example inside the contract.
394
+
395
+ ```ruby
396
+ module HowlingContract
397
+ extend RSpec::SleepingKingStudios::Contract
398
+
399
+ it { expect(werewolf).to respond_to(:howl) }
400
+ end
401
+ ```
402
+
403
+ #### `::shared_context`
404
+
405
+ Defines a shared example group.
406
+
407
+ ```ruby
408
+ module MoonContract
409
+ extend RSpec::SleepingKingStudios::Contract
410
+
411
+ shared_context 'when the moon is full' do
412
+ before(:example) { moon.phase = :full }
413
+ end
414
+ end
415
+ ```
416
+
417
+ **Note:** When the `Contract` is included in an RSpec example group, any shared example groups defined at the top level of a contract are also included in that example group, even outside of the contract itself. This may cause namespace collisions with shared example groups defined elsewhere in the example group or by other included contracts.
418
+
419
+ #### `::shared_examples`
420
+
421
+ Defines a shared example group.
422
+
423
+ ```ruby
424
+ module HairContract
425
+ extend RSpec::SleepingKingStudios::Contract
426
+
427
+ shared_examples 'should be hairy' do
428
+ describe '#hairy?' do
429
+ it { expect(werewolf.hairy?).to be true }
430
+ end
431
+ end
432
+ end
433
+ ```
434
+
435
+ **Note:** When the `Contract` is included in an RSpec example group, any shared example groups defined at the top level of a contract are also included in that example group, even outside of the contract itself. This may cause namespace collisions with shared example groups defined elsewhere in the example group or by other included contracts.
436
+
437
+ ### Developing Contracts
438
+
439
+ ```ruby
440
+ module VampireContract
441
+ extend RSpec::SleepingKingStudios::Contract
442
+ extend RSpec::SleepingKingStudios::Contracts::Development
443
+
444
+ fdescribe '#drink' do
445
+ it { expect(drink 'blood').to be true }
446
+
447
+ xit { expect(drink 'holy water').to be false }
448
+ end
449
+
450
+ pending
451
+ end
452
+ ```
453
+
454
+ The `RSpec::SleepingKingStudios::Contracts::Development` module provides methods for defining focused or pending examples and example groups. These are intended for use when developing a contract, and should not be included in the final version. Having skipped or focused example groups in a shared contract can have unexpected effects when the contract is included by the end user.
455
+
456
+ #### `::fcontext`
457
+
458
+ Defines a focused example group inside the contract.
459
+
460
+ #### `::fdescribe`
461
+
462
+ Defines a focused example group inside the contract.
463
+
464
+ #### `::fit`
465
+
466
+ Defines a focused example inside the contract.
467
+
468
+ #### `::pending`
469
+
470
+ Marks the contract as pending.
471
+
472
+ #### `::xcontext`
473
+
474
+ Defines a skipped example group inside the contract.
475
+
476
+ #### `::xdescribe`
477
+
478
+ Defines a skipped example group inside the contract.
479
+
480
+ #### `::xit`
481
+
482
+ Defines a skipped example inside the contract.
483
+
484
+ ## Matchers
326
485
 
327
486
  To enable a custom matcher, simply require the associated file. Matchers can be required individually or by category:
328
487
 
@@ -447,6 +606,22 @@ Checks if the object aliases the specified method with the specified other name.
447
606
 
448
607
  * **`#as`:** Required. Expects one String or Symbol, which is the name of the generated method.
449
608
 
609
+ #### `#be_a_uuid` Matcher
610
+
611
+ require 'rspec/sleeping_king_studios/matchers/core/be_a_uuid'
612
+
613
+ **Aliases:** `#a_uuid`.
614
+
615
+ **How To Use:**
616
+
617
+ # With an object comparison.
618
+ expect(string).to be_a_uuid
619
+
620
+ # Inside a composable matcher.
621
+ expect(array).to include(a_uuid)
622
+
623
+ **Parameters:** None.
624
+
450
625
  #### `#be_boolean` Matcher
451
626
 
452
627
  require 'rspec/sleeping_king_studios/matchers/core/be_boolean'
@@ -488,6 +663,52 @@ Verifies that the actual object can be constructed using `::new`. Can take an op
488
663
  * **`#with_keywords`:** (also `and_keywords`) Expects one or more String or Symbol arguments. Verifies that the class's constructor accepts each provided keyword or has a variadic keyword of the form `**params`. As of 2.1.0 and required keywords, verifies that all required keywords are provided.
489
664
  * **`#with_arbitrary_keywords`:** (also `and_arbitrary_keywords`) No parameters. Verifies that the class's constructor accepts any keyword arguments via a variadic keyword of the form `**params`.
490
665
 
666
+ #### `#deep_match` Matcher
667
+
668
+ require 'rspec/sleeping_king_studios/matchers/core/deep_match'
669
+
670
+ Performs a recursive comparison between two object or data structures. Also supports using RSpec matchers as values in the expected object.
671
+
672
+ **How To Use:**
673
+
674
+ expected = {
675
+ status: 200,
676
+ body: {
677
+ order: {
678
+ id: an_instance_of(Integer),
679
+ total: '9.99'
680
+ }
681
+ }
682
+ }
683
+ expect(response).to deep_match(expected)
684
+
685
+ When the value does not match the expectation, the failure message will provide a detailed diff showing added, missing, and changed values.
686
+
687
+ response = {
688
+ status: 400,
689
+ body: {
690
+ order: {
691
+ fulfilled: false,
692
+ total: '19.99'
693
+ }
694
+ },
695
+ errors: ['Insufficient funds']
696
+ }
697
+ expect(response).to deep_match(expected)
698
+ # Failure/Error: expect(response).to deep_match(expected)
699
+ #
700
+ # expected: == {:body=>{:order=>{:id=>an instance of Integer, :total=>"9.99"}}, :status=>200}
701
+ # got: {:status=>400, :body=>{:order=>{:fulfilled=>false, :total=>"19.99"}}, :errors=>["Insufficient funds"]}
702
+ #
703
+ # (compared using HashDiff)
704
+ #
705
+ # Diff:
706
+ # + :body.:order.:fulfilled => got false
707
+ # - :body.:order.:id => expected an instance of Integer
708
+ # ~ :body.:order.:total => expected "9.99", got "19.99"
709
+ # + :errors => got ["Insufficient funds"]
710
+ # ~ :status => expected 200, got 400
711
+
491
712
  #### `#delegate_method` Matcher
492
713
 
493
714
  require 'rspec/sleeping_king_studios/matchers/core/delegate_method'
@@ -8,14 +8,14 @@ require 'rspec/sleeping_king_studios/matchers/base_matcher'
8
8
  module RSpec::SleepingKingStudios::Examples::RSpecMatcherExamples
9
9
  extend RSpec::SleepingKingStudios::Concerns::SharedExampleGroup
10
10
 
11
- private def config
11
+ private def rspec_config
12
12
  RSpec.configuration.sleeping_king_studios
13
- end # method config
13
+ end # method rspec_config
14
14
 
15
15
  private def compare_message actual, expected
16
16
  case expected
17
17
  when String
18
- if config.examples.match_string_failure_message_as == :exact
18
+ if rspec_config.examples.match_string_failure_message_as == :exact
19
19
  expect(actual).to be == expected
20
20
  else
21
21
  expect(actual).to include expected
@@ -29,8 +29,12 @@ module RSpec::SleepingKingStudios::Examples::RSpecMatcherExamples
29
29
  end # when
30
30
  end # method compare_message
31
31
 
32
+ private def format_expected(object)
33
+ RSpec::Support::ObjectFormatter.format(object)
34
+ end
35
+
32
36
  private def handle_missing_failure_message message
33
- case config.examples.handle_missing_failure_message_with
37
+ case rspec_config.examples.handle_missing_failure_message_with
34
38
  when :pending
35
39
  skip message
36
40
  when :exception
@@ -17,7 +17,12 @@ module RSpec::SleepingKingStudios::Matchers::BuiltIn
17
17
  # should return true if and only if the item matches the desired
18
18
  # predicate.
19
19
  def initialize *expected, &block
20
- expected << block if block_given?
20
+ if block_given?
21
+ SleepingKingStudios::Tools::CoreTools
22
+ .deprecate('IncludeMatcher with a block')
23
+
24
+ expected << block
25
+ end
21
26
 
22
27
  if expected.empty? && !allow_empty_matcher?
23
28
  raise ArgumentError,
@@ -87,18 +87,18 @@ module RSpec::SleepingKingStudios::Matchers::BuiltIn
87
87
 
88
88
  method =
89
89
  begin
90
- actual.method(method_name)
90
+ if actual.is_a?(Class) && method_name.intern == :new
91
+ actual.instance_method(:initialize)
92
+ else
93
+ actual.method(method_name)
94
+ end
91
95
  rescue NameError
92
- nil
93
- end # unless
96
+ @failing_method_reasons[method_name] = {
97
+ :is_not_a_method => true
98
+ } # end hash
94
99
 
95
- unless method.is_a?(Method)
96
- @failing_method_reasons[method_name] = {
97
- :is_not_a_method => true
98
- } # end hash
99
-
100
- next false
101
- end # unless
100
+ next false
101
+ end
102
102
 
103
103
  next true unless method_signature_expectation?
104
104
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literals: true
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/core/be_a_uuid_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/macros'
5
+
6
+ module RSpec::SleepingKingStudios::Matchers::Macros
7
+ # @see RSpec::SleepingKingStudios::Matchers::Core::BeAUuidMatcher#matches?
8
+ def be_a_uuid
9
+ RSpec::SleepingKingStudios::Matchers::Core::BeAUuidMatcher.new
10
+ end
11
+
12
+ # @!method a_uuid
13
+ # @see RSpec::SleepingKingStudios::Matchers::Core::BeBooleanMatcher#matches?
14
+ alias_matcher :a_uuid, :be_a_uuid do |description|
15
+ 'a UUID'
16
+ end
17
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literals: true
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/base_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/core'
5
+
6
+ module RSpec::SleepingKingStudios::Matchers::Core
7
+ # Matcher for testing whether an object is a UUID string.
8
+ #
9
+ # @since 2.5.0
10
+ class BeAUuidMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
11
+ # (see BaseMatcher#description)
12
+ def description
13
+ 'be a UUID'
14
+ end
15
+
16
+ # (see BaseMatcher#failure_message)
17
+ def failure_message
18
+ message = super() + ', but '
19
+
20
+ return message + 'was not a String' unless string?
21
+
22
+ return message + 'was too short' if too_short?
23
+
24
+ return message + 'was too long' if too_long?
25
+
26
+ return message + 'has invalid characters' if invalid_characters?
27
+
28
+ return message + 'the format is invalid' unless valid_format?
29
+
30
+ message
31
+ end
32
+
33
+ # Checks if the object is a UUID string.
34
+ #
35
+ # @param [Object] actual The object to check.
36
+ #
37
+ # @return [Boolean] true if the object is a string with the correct format;
38
+ # otherwise false.
39
+ def matches?(actual)
40
+ super
41
+
42
+ string? && valid_length? && valid_characters? && valid_format?
43
+ end
44
+
45
+ private
46
+
47
+ def invalid_characters?
48
+ @invalid_characters ||= @actual.match?(/[^A-Fa-f0-9\-]/)
49
+ end
50
+
51
+ def string?
52
+ @string ||= @actual.is_a?(String)
53
+ end
54
+
55
+ def too_long?
56
+ @too_long ||= @actual.length > 36
57
+ end
58
+
59
+ def too_short?
60
+ @too_short ||= @actual.length < 36
61
+ end
62
+
63
+ def uuid_format
64
+ chars = '[A-Fa-f0-9\-]'
65
+
66
+ /\A#{chars}{8}-#{chars}{4}-#{chars}{4}-#{chars}{4}-#{chars}{12}\z/
67
+ end
68
+
69
+ def valid_characters?
70
+ !invalid_characters?
71
+ end
72
+
73
+ def valid_format?
74
+ @valid_format || @actual.match?(uuid_format)
75
+ end
76
+
77
+ def valid_length?
78
+ !too_short? && !too_long?
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literals: true
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/core/deep_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/macros'
5
+
6
+ module RSpec::SleepingKingStudios::Matchers::Macros
7
+ # @see RSpec::SleepingKingStudios::Matchers::Core::BeBooleanMatcher#matches?
8
+ def deep_match(expected)
9
+ RSpec::SleepingKingStudios::Matchers::Core::DeepMatcher.new(expected)
10
+ end
11
+ end
@@ -0,0 +1,219 @@
1
+ # frozen_string_literals: true
2
+
3
+ require 'hashdiff'
4
+
5
+ require 'rspec/sleeping_king_studios/matchers/base_matcher'
6
+ require 'rspec/sleeping_king_studios/matchers/core'
7
+
8
+ module RSpec::SleepingKingStudios::Matchers::Core
9
+ # Matcher for performing a deep comparison between two objects.
10
+ #
11
+ # @since 2.5.0
12
+ class DeepMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
13
+ # @param [Object] expected The expected object.
14
+ def initialize(expected)
15
+ @expected = expected
16
+ end
17
+
18
+ # (see BaseMatcher#description)
19
+ def description
20
+ "match #{format_expected(@expected)}"
21
+ end # method description
22
+
23
+ # Inverse of #matches? method.
24
+ #
25
+ # @param [Object] actual The object to check.
26
+ #
27
+ # @return [Boolean] true if the actual object does not match the
28
+ # expectation, otherwise true.
29
+ #
30
+ # @see #matches?
31
+ def does_not_match? actual
32
+ super
33
+
34
+ if matcher?(@expected)
35
+ delegate_to_negated_matcher(@expected)
36
+ elsif @expected.is_a?(Array) && actual.is_a?(Array)
37
+ diff_arrays_negated
38
+ elsif @expected.is_a?(Hash) && actual.is_a?(Hash)
39
+ diff_hashes_negated
40
+ else
41
+ delegate_to_negated_matcher(equality_matcher)
42
+ end
43
+
44
+ !@matches
45
+ end
46
+
47
+ # (see BaseMatcher#failure_message)
48
+ def failure_message
49
+ @failure_message
50
+ end
51
+
52
+ # (see BaseMatcher#failure_message_when_negated)
53
+ def failure_message_when_negated
54
+ @failure_message_when_negated
55
+ end
56
+
57
+ # Performs a deep comparison between the actual object and the expected
58
+ # object. The type of comparison depends on the type of the expected object:
59
+ #
60
+ # - If the expected object is an RSpec matcher, the #matches? method on the
61
+ # matcher is called with the expected object.
62
+ # - If the expected object is an Array, then each item is compared based on
63
+ # the type of the expected item.
64
+ # - If the expected object is a Hash, then the keys must match and each
65
+ # value is compared based on the type of the expected value.
66
+ # - Otherwise, the two objects are compared using an equality comparison.
67
+ #
68
+ # @param [Object] actual The object to check.
69
+ #
70
+ # @return [Boolean] true if the actual object matches the expectation,
71
+ # otherwise false.
72
+ def matches?(actual)
73
+ super
74
+
75
+ if matcher?(@expected)
76
+ delegate_to_matcher(@expected)
77
+ elsif @expected.is_a?(Array) && actual.is_a?(Array)
78
+ diff_arrays
79
+ elsif @expected.is_a?(Hash) && actual.is_a?(Hash)
80
+ diff_hashes
81
+ else
82
+ delegate_to_matcher(equality_matcher)
83
+ end
84
+
85
+ @matches
86
+ end
87
+
88
+ private
89
+
90
+ def compare_arrays(expected, actual)
91
+ compare_hashes({ _ary: expected }, { _ary: actual })
92
+ .map { |(char, path, *values)| [char, path[1..-1], *values] }
93
+ end
94
+
95
+ def compare_hashes(expected, actual)
96
+ HashDiff.diff(expected, actual, array_path: true, use_lcs: false) \
97
+ do |path, exp, act|
98
+ # Handle missing keys with matcher values.
99
+ next nil unless nested_key?(actual, path)
100
+
101
+ next exp.matches?(act) if matcher?(exp)
102
+ end
103
+ end
104
+
105
+ def delegate_to_matcher(matcher)
106
+ @matches = matcher.matches?(actual)
107
+
108
+ return if @matches
109
+
110
+ @failure_message = matcher.failure_message
111
+ end
112
+
113
+ def delegate_to_negated_matcher(matcher)
114
+ @matches =
115
+ if matcher.respond_to?(:does_not_match?)
116
+ !matcher.does_not_match?(actual)
117
+ else
118
+ matcher.matches?(actual)
119
+ end
120
+
121
+ return unless @matches
122
+
123
+ @failure_message_when_negated = matcher.failure_message_when_negated
124
+ end
125
+
126
+ def diff_arrays
127
+ diff = compare_arrays(@expected, actual)
128
+ @matches = diff.empty?
129
+
130
+ @failure_message = format_message(diff)
131
+ end
132
+
133
+ def diff_arrays_negated
134
+ diff = compare_arrays(@expected, actual)
135
+ @matches = diff.empty?
136
+
137
+ @failure_message_when_negated =
138
+ "`expect(#{format_expected(@expected)}).not_to be == #{actual.inspect}`"
139
+ end
140
+
141
+ def diff_hashes
142
+ diff = compare_hashes(@expected, actual)
143
+ @matches = diff.empty?
144
+
145
+ @failure_message = format_message(diff)
146
+ end
147
+
148
+ def diff_hashes_negated
149
+ diff = compare_hashes(@expected, actual)
150
+ @matches = diff.empty?
151
+
152
+ @failure_message_when_negated =
153
+ "`expect(#{format_expected(@expected)}).not_to be == #{actual.inspect}`"
154
+ end
155
+
156
+ def equality_matcher
157
+ matchers_delegate.be == @expected
158
+ end
159
+
160
+ def format_diff(diff)
161
+ diff
162
+ .sort_by { |(char, path, *_values)| [path.map(&:to_s)] }
163
+ .map { |item| format_diff_item(*item) }
164
+ .join "\n"
165
+ end
166
+
167
+ def format_diff_item(char, path, *values)
168
+ "#{char} #{format_diff_path(path)} => #{format_diff_values(char, values)}"
169
+ end
170
+
171
+ def format_diff_path(path)
172
+ path.map(&:inspect).join('.')
173
+ end
174
+
175
+ def format_diff_values(char, values)
176
+ case char
177
+ when '-'
178
+ "expected #{format_expected(values.first)}"
179
+ when '~'
180
+ "expected #{format_expected(values.first)}, got #{values.last.inspect}"
181
+ when '+'
182
+ "got #{values.last.inspect}"
183
+ end
184
+ end
185
+
186
+ def format_expected(object)
187
+ RSpec::Support::ObjectFormatter.format(object)
188
+ end
189
+
190
+ def format_message(diff)
191
+ "expected: == #{format_expected(@expected)}\n" \
192
+ " got: #{@actual.inspect}\n" \
193
+ "\n" \
194
+ "(compared using HashDiff)\n" \
195
+ "\n" \
196
+ "Diff:\n" \
197
+ "#{format_diff(diff)}"
198
+ end
199
+
200
+ def matcher?(object)
201
+ %i[description failure_message failure_message_when_negated matches?]
202
+ .all? { |method_name| object.respond_to?(method_name) }
203
+ end
204
+
205
+ def matchers_delegate
206
+ Object.new.extend RSpec::Matchers
207
+ end
208
+
209
+ def nested_key?(object, path)
210
+ key = path.last
211
+ object = object.dig(*path[0...-1]) if path.size > 1
212
+
213
+ return object.key?(key) if object.is_a?(Hash)
214
+ return object.size > key if object.is_a?(Array)
215
+
216
+ false
217
+ end
218
+ end
219
+ end
@@ -125,6 +125,8 @@ module RSpec::SleepingKingStudios::Matchers::Core
125
125
 
126
126
  # (see BaseMatcher#matches?)
127
127
  def matches? actual
128
+ SleepingKingStudios::Tools::CoreTools.deprecate('DelegateMethodMatcher')
129
+
128
130
  super
129
131
 
130
132
  raise ArgumentError.new('must specify a target') if @target.nil?
@@ -187,14 +189,22 @@ module RSpec::SleepingKingStudios::Matchers::Core
187
189
 
188
190
  private
189
191
 
190
- def call_method arguments, expected_return = DEFAULT_EXPECTED_RETURN
192
+ def call_method(arguments:, keywords:, expected_return: DEFAULT_EXPECTED_RETURN)
191
193
  if @expected_block
192
194
  @received_block = false
193
195
  block = ->(*args, **kwargs, &block) {}
194
196
 
195
- return_value = @actual.send(@expected, *arguments, &block)
197
+ if keywords.empty?
198
+ return_value = @actual.send(@expected, *arguments, &block)
199
+ else
200
+ return_value = @actual.send(@expected, *arguments, **keywords, &block)
201
+ end
196
202
  else
197
- return_value = @actual.send(@expected, *arguments)
203
+ if keywords.empty?
204
+ return_value = @actual.send(@expected, *arguments)
205
+ else
206
+ return_value = @actual.send(@expected, *arguments, **keywords)
207
+ end
198
208
  end
199
209
 
200
210
  @received_return_values << return_value
@@ -215,19 +225,22 @@ module RSpec::SleepingKingStudios::Matchers::Core
215
225
  def delegates_method?
216
226
  stub_target!
217
227
 
218
- args = @expected_arguments.dup
219
- args << @expected_keywords unless @expected_keywords.empty?
220
-
221
228
  if @expected_return_values.empty?
222
- call_method(args)
229
+ call_method(arguments: @expected_arguments, keywords: @expected_keywords)
223
230
  else
224
231
  @expected_return_values.each do |return_value|
225
- call_method(args, return_value)
232
+ call_method(arguments: @expected_arguments, keywords: @expected_keywords, expected_return: return_value)
226
233
  end # each
227
234
  end # if-else
228
235
 
229
236
  matcher = RSpec::Mocks::Matchers::HaveReceived.new(@expected)
230
- matcher = matcher.with(*args) unless args.empty?
237
+ if !@expected_arguments.empty? && !@expected_keywords.empty?
238
+ matcher = matcher.with(*@expected_arguments, **@expected_keywords)
239
+ elsif !@expected_arguments.empty?
240
+ matcher = matcher.with(*@expected_arguments)
241
+ elsif !@expected_keywords.empty?
242
+ matcher = matcher.with(**@expected_keywords)
243
+ end
231
244
 
232
245
  unless @expected_return_values.empty?
233
246
  matcher = matcher.exactly(@expected_return_values.count).times
@@ -1,3 +1,5 @@
1
+ # frozen_string_literals: true
2
+
1
3
  require 'rspec/sleeping_king_studios/matchers/base_matcher'
2
4
  require 'rspec/sleeping_king_studios/matchers/core'
3
5
  require 'rspec/sleeping_king_studios/support/value_spy'
@@ -76,7 +78,7 @@ module RSpec::SleepingKingStudios::Matchers::Core
76
78
  unless @match_initial_value.nil? || @match_initial_value
77
79
  return "expected #{value_spy.description} to have initially " \
78
80
  "been #{@expected_initial_value.inspect}, but was " \
79
- "#{initial_value.inspect}"
81
+ "#{value_spy.initial_inspect}"
80
82
  end
81
83
 
82
84
  message = "expected #{value_spy.description} to have changed"
@@ -107,13 +109,13 @@ module RSpec::SleepingKingStudios::Matchers::Core
107
109
  unless @match_initial_value.nil? || @match_initial_value
108
110
  return "expected #{value_spy.description} to have initially " \
109
111
  "been #{@expected_initial_value.inspect}, but was " \
110
- "#{initial_value.inspect}"
112
+ "#{value_spy.initial_inspect}"
111
113
  end
112
114
 
113
115
  message = "expected #{value_spy.description} not to have changed"
114
116
 
115
117
  message <<
116
- ", but did change from #{initial_value.inspect} to " <<
118
+ ", but did change from #{value_spy.initial_inspect} to " <<
117
119
  current_value.inspect
118
120
 
119
121
  message
@@ -1,3 +1,5 @@
1
+ # frozen_string_literals: true
2
+
1
3
  require 'rspec/sleeping_king_studios/support'
2
4
 
3
5
  module RSpec::SleepingKingStudios::Support
@@ -23,8 +25,8 @@ module RSpec::SleepingKingStudios::Support
23
25
  # value.initial_value #=> 4
24
26
  # value.current_value #=> 3
25
27
  class ValueSpy
26
- # @overload initialize(object, method_name)
27
- # @param [Object] object The object to watch.
28
+ # @overload initialize(receiver, method_name)
29
+ # @param [Object] receiver The object to watch.
28
30
  #
29
31
  # @param [Symbol, String] method_name The name of the method to watch.
30
32
  #
@@ -32,23 +34,34 @@ module RSpec::SleepingKingStudios::Support
32
34
  # @yield The value to watch. The block will be called each time the value
33
35
  # is requested, and the return value of the block will be given as the
34
36
  # current value.
35
- def initialize(object = nil, method_name = nil, &block)
37
+ def initialize(receiver = nil, method_name = nil, &block)
36
38
  @observed_block = if block_given?
37
39
  block
38
40
  else
39
41
  @method_name = method_name
40
42
 
41
- -> { object.send(method_name) }
43
+ -> { receiver.send(method_name) }
42
44
  end
43
45
 
44
- @initial_value = current_value
46
+ @receiver = receiver
47
+ @initial_hash = current_value.hash
48
+ @initial_inspect = current_value.inspect
49
+ @initial_value = current_value
45
50
  end
46
51
 
52
+ # @return [Integer] the hash of the watched value at the time the spy was
53
+ # initialized.
54
+ attr_reader :initial_hash
55
+
56
+ # @return [String] the string representation of the watched value at the
57
+ # time the spy was initialized
58
+ attr_reader :initial_inspect
59
+
47
60
  # @return [Object] the watched value at the time the spy was initialized.
48
61
  attr_reader :initial_value
49
62
 
50
63
  def changed?
51
- !RSpec::Support::FuzzyMatcher.values_match?(initial_value, current_value)
64
+ initial_value != current_value || initial_hash != current_value.hash
52
65
  end
53
66
 
54
67
  # @return [Object] the watched value when #current_value is called.
@@ -61,7 +74,19 @@ module RSpec::SleepingKingStudios::Support
61
74
  def description
62
75
  return 'result' unless @method_name
63
76
 
64
- "##{@method_name}"
77
+ format_message
78
+ end
79
+
80
+ private
81
+
82
+ attr_reader :method_name
83
+
84
+ attr_reader :receiver
85
+
86
+ def format_message
87
+ return "#{receiver}.#{method_name}" if receiver.is_a?(Module)
88
+
89
+ "#{receiver.class}##{method_name}"
65
90
  end
66
91
  end
67
92
  end
@@ -1,4 +1,4 @@
1
- # lib/rspec/sleeping_king_studios/version.rb
1
+ # frozen_string_literals: true
2
2
 
3
3
  module RSpec
4
4
  module SleepingKingStudios
@@ -11,13 +11,13 @@ module RSpec
11
11
  # Major version.
12
12
  MAJOR = 2
13
13
  # Minor version.
14
- MINOR = 4
14
+ MINOR = 6
15
15
  # Patch version.
16
16
  PATCH = 0
17
17
  # Prerelease version.
18
- PRERELEASE = nil
18
+ PRERELEASE = :rc
19
19
  # Build metadata.
20
- BUILD = nil
20
+ BUILD = 0
21
21
 
22
22
  # Generates the gem version string from the Version constants.
23
23
  #
@@ -36,9 +36,9 @@ module RSpec
36
36
  str << ".#{build}" unless build.nil? || (build.respond_to?(:empty?) && build.empty?)
37
37
 
38
38
  str
39
- end # class method to_version
40
- end # module
39
+ end
40
+ end
41
41
 
42
42
  VERSION = Version.to_gem_version
43
- end # module
44
- end # module
43
+ end
44
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-sleeping_king_studios
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.6.0.rc.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob "Merlin" Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-19 00:00:00.000000000 Z
11
+ date: 2021-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hashdiff
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.8
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.8
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rspec
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -28,16 +42,22 @@ dependencies:
28
42
  name: sleeping_king_studios-tools
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
- - - "~>"
45
+ - - ">="
32
46
  - !ruby/object:Gem::Version
33
- version: '0.7'
47
+ version: 0.8.0
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: '2'
34
51
  type: :runtime
35
52
  prerelease: false
36
53
  version_requirements: !ruby/object:Gem::Requirement
37
54
  requirements:
38
- - - "~>"
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 0.8.0
58
+ - - "<"
39
59
  - !ruby/object:Gem::Version
40
- version: '0.7'
60
+ version: '2'
41
61
  - !ruby/object:Gem::Dependency
42
62
  name: appraisal
43
63
  requirement: !ruby/object:Gem::Requirement
@@ -106,14 +126,20 @@ dependencies:
106
126
  requirements:
107
127
  - - "~>"
108
128
  - !ruby/object:Gem::Version
109
- version: '0.1'
129
+ version: '0.4'
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 0.4.1
110
133
  type: :development
111
134
  prerelease: false
112
135
  version_requirements: !ruby/object:Gem::Requirement
113
136
  requirements:
114
137
  - - "~>"
115
138
  - !ruby/object:Gem::Version
116
- version: '0.1'
139
+ version: '0.4'
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 0.4.1
117
143
  - !ruby/object:Gem::Dependency
118
144
  name: aruba
119
145
  requirement: !ruby/object:Gem::Requirement
@@ -151,7 +177,7 @@ dependencies:
151
177
  version: '3.0'
152
178
  - - "<"
153
179
  - !ruby/object:Gem::Version
154
- version: '6.0'
180
+ version: '7.0'
155
181
  type: :development
156
182
  prerelease: false
157
183
  version_requirements: !ruby/object:Gem::Requirement
@@ -161,7 +187,7 @@ dependencies:
161
187
  version: '3.0'
162
188
  - - "<"
163
189
  - !ruby/object:Gem::Version
164
- version: '6.0'
190
+ version: '7.0'
165
191
  description: |2
166
192
  A collection of RSpec patches and custom matchers. The features can be
167
193
  included individually or by category. For more information, check out the
@@ -217,10 +243,14 @@ files:
217
243
  - lib/rspec/sleeping_king_studios/matchers/core/alias_method.rb
218
244
  - lib/rspec/sleeping_king_studios/matchers/core/alias_method_matcher.rb
219
245
  - lib/rspec/sleeping_king_studios/matchers/core/all.rb
246
+ - lib/rspec/sleeping_king_studios/matchers/core/be_a_uuid.rb
247
+ - lib/rspec/sleeping_king_studios/matchers/core/be_a_uuid_matcher.rb
220
248
  - lib/rspec/sleeping_king_studios/matchers/core/be_boolean.rb
221
249
  - lib/rspec/sleeping_king_studios/matchers/core/be_boolean_matcher.rb
222
250
  - lib/rspec/sleeping_king_studios/matchers/core/construct.rb
223
251
  - lib/rspec/sleeping_king_studios/matchers/core/construct_matcher.rb
252
+ - lib/rspec/sleeping_king_studios/matchers/core/deep_match.rb
253
+ - lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb
224
254
  - lib/rspec/sleeping_king_studios/matchers/core/delegate_method.rb
225
255
  - lib/rspec/sleeping_king_studios/matchers/core/delegate_method_matcher.rb
226
256
  - lib/rspec/sleeping_king_studios/matchers/core/have_changed.rb
@@ -247,7 +277,9 @@ files:
247
277
  homepage: http://sleepingkingstudios.com
248
278
  licenses:
249
279
  - MIT
250
- metadata: {}
280
+ metadata:
281
+ bug_tracker_uri: https://github.com/sleepingkingstudios/rspec-sleeping_king_studios/issues
282
+ source_code_uri: https://github.com/sleepingkingstudios/rspec-sleeping_king_studios
251
283
  post_install_message:
252
284
  rdoc_options: []
253
285
  require_paths:
@@ -259,12 +291,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
259
291
  version: '0'
260
292
  required_rubygems_version: !ruby/object:Gem::Requirement
261
293
  requirements:
262
- - - ">="
294
+ - - ">"
263
295
  - !ruby/object:Gem::Version
264
- version: '0'
296
+ version: 1.3.1
265
297
  requirements: []
266
- rubyforge_project:
267
- rubygems_version: 2.7.6
298
+ rubygems_version: 3.1.2
268
299
  signing_key:
269
300
  specification_version: 4
270
301
  summary: A collection of RSpec patches and custom matchers.