rspec-sleeping_king_studios 2.7.0 → 2.8.0.rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +228 -9
- data/config/rubocop-rspec.yml +41 -0
- data/lib/rspec/sleeping_king_studios/concerns/example_constants.rb +107 -74
- data/lib/rspec/sleeping_king_studios/concerns/memoized_helpers.rb +19 -0
- data/lib/rspec/sleeping_king_studios/concerns/shared_example_group.rb +5 -2
- data/lib/rspec/sleeping_king_studios/concerns.rb +8 -3
- data/lib/rspec/sleeping_king_studios/configuration.rb +45 -37
- data/lib/rspec/sleeping_king_studios/deferred/call.rb +74 -0
- data/lib/rspec/sleeping_king_studios/deferred/calls/example.rb +42 -0
- data/lib/rspec/sleeping_king_studios/deferred/calls/example_group.rb +42 -0
- data/lib/rspec/sleeping_king_studios/deferred/calls/hook.rb +64 -0
- data/lib/rspec/sleeping_king_studios/deferred/calls/included_examples.rb +34 -0
- data/lib/rspec/sleeping_king_studios/deferred/calls/shared_examples.rb +41 -0
- data/lib/rspec/sleeping_king_studios/deferred/calls.rb +19 -0
- data/lib/rspec/sleeping_king_studios/deferred/consumer.rb +159 -0
- data/lib/rspec/sleeping_king_studios/deferred/definitions.rb +42 -0
- data/lib/rspec/sleeping_king_studios/deferred/dependencies.rb +138 -0
- data/lib/rspec/sleeping_king_studios/deferred/dsl/example_constants.rb +72 -0
- data/lib/rspec/sleeping_king_studios/deferred/dsl/example_groups.rb +69 -0
- data/lib/rspec/sleeping_king_studios/deferred/dsl/examples.rb +84 -0
- data/lib/rspec/sleeping_king_studios/deferred/dsl/hooks.rb +125 -0
- data/lib/rspec/sleeping_king_studios/deferred/dsl/memoized_helpers.rb +123 -0
- data/lib/rspec/sleeping_king_studios/deferred/dsl/shared_examples.rb +128 -0
- data/lib/rspec/sleeping_king_studios/deferred/dsl.rb +26 -0
- data/lib/rspec/sleeping_king_studios/deferred/examples.rb +83 -0
- data/lib/rspec/sleeping_king_studios/deferred/missing.rb +46 -0
- data/lib/rspec/sleeping_king_studios/deferred/provider.rb +164 -0
- data/lib/rspec/sleeping_king_studios/deferred.rb +142 -0
- data/lib/rspec/sleeping_king_studios/matchers/built_in/include_matcher.rb +85 -70
- data/lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb +28 -23
- data/lib/rspec/sleeping_king_studios/sandbox.rb +105 -0
- data/lib/rspec/sleeping_king_studios/version.rb +4 -3
- data/lib/rspec/sleeping_king_studios.rb +10 -4
- metadata +36 -141
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0fdf0b5b82bec68224de25df61b57af9d3910c48b30676fd96dafc0862ed5c3
|
4
|
+
data.tar.gz: 96e93e8c3438a94ab2f549cb5487ff6f3f1d3a72f8d935973eab796126eb5aa1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2d2c014fccc9f46abf5a1a45d14571b46e33f56edc6c3d7bb23326b8da15624ff02a24383f9da135d38a6c15fd39a171da5037c81d5d63dabeabadc68736699
|
7
|
+
data.tar.gz: b972588c34e3dbf36778330e3006e432c62c000823686186b1004a9b50a3b2ee4193b6d0f6d932f59a8daeb5eef7218bc5a45db4813b411d88b1a803842a6174
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,30 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 2.8.0
|
4
|
+
|
5
|
+
Updated minimum `Hashdiff` version to `~> 1.1`.
|
6
|
+
|
7
|
+
Extracted `rubocop-rspec` configuration. To add support for `RSpec::SleepingKingStudios` extensions to `rubocop-rspec`, add the following to your `.rubocop.yml` configuration file:
|
8
|
+
|
9
|
+
```yml
|
10
|
+
inherit_gem:
|
11
|
+
rspec-sleeping_king_studios: 'config/rubocop-rspec.yml'
|
12
|
+
```
|
13
|
+
|
14
|
+
### Concerns
|
15
|
+
|
16
|
+
#### Memoized Helpers
|
17
|
+
|
18
|
+
Implemented the `let?` memoized helper, which defines a helper method if there is not already a method with the given name.
|
19
|
+
|
20
|
+
### Deferred Examples
|
21
|
+
|
22
|
+
Implemented deferred examples, which provide an alternative implementation for sharing specifications between projects.
|
23
|
+
|
24
|
+
### Sandbox
|
25
|
+
|
26
|
+
Implemented `RSpec::SleepingKingStudios::Sandbox` for running test files in an isolated environment.
|
27
|
+
|
3
28
|
## 2.7.0
|
4
29
|
|
5
30
|
### Concerns
|
data/README.md
CHANGED
@@ -1,16 +1,32 @@
|
|
1
|
-
# RSpec::SleepingKingStudios
|
1
|
+
# RSpec::SleepingKingStudios
|
2
2
|
|
3
3
|
A collection of matchers and extensions to ease TDD/BDD using RSpec. Extends built-in matchers with new functionality, such as support for Ruby 2.0+ keyword arguments, and adds new matchers for testing boolean-ness, object reader/writer properties, object constructor arguments, ActiveModel validations, and more. Also defines shared example groups for more expressive testing.
|
4
4
|
|
5
|
+
<blockquote>
|
6
|
+
Read The
|
7
|
+
<a href="https://www.sleepingkingstudios.com/rspec-sleeping_king_studios" target="_blank">
|
8
|
+
Documentation
|
9
|
+
</a>
|
10
|
+
</blockquote>
|
11
|
+
|
5
12
|
## About
|
6
13
|
|
14
|
+
### Setup
|
15
|
+
|
16
|
+
To add support for `RSpec::SleepingKingStudios` extensions to `rubocop-rspec`, add the following to your `.rubocop.yml` configuration file:
|
17
|
+
|
18
|
+
```yml
|
19
|
+
inherit_gem:
|
20
|
+
rspec-sleeping_king_studios: 'config/rubocop-rspec.yml'
|
21
|
+
```
|
22
|
+
|
7
23
|
### Compatibility
|
8
24
|
|
9
25
|
RSpec::SleepingKingStudios is tested against the following dependencies:
|
10
26
|
|
11
|
-
- Ruby (MRI)
|
12
|
-
- RSpec versions 3.
|
13
|
-
- ActiveModel versions
|
27
|
+
- Ruby (MRI) 3.1 through 3.4
|
28
|
+
- RSpec versions 3.9 through 3.13
|
29
|
+
- ActiveModel versions 6.1 through 8.0
|
14
30
|
|
15
31
|
### Documentation
|
16
32
|
|
@@ -18,7 +34,7 @@ Documentation is generated using [YARD](https://yardoc.org/), and can be generat
|
|
18
34
|
|
19
35
|
### License
|
20
36
|
|
21
|
-
Copyright (c) 2013-
|
37
|
+
Copyright (c) 2013-2025 Rob Smith
|
22
38
|
|
23
39
|
RSpec::SleepingKingStudios is released under the [MIT License](https://opensource.org/licenses/MIT).
|
24
40
|
|
@@ -34,7 +50,6 @@ To contribute code, please fork the repository, make the desired updates, and th
|
|
34
50
|
|
35
51
|
Please note that the `RSpec::SleepingKingStudios` project is released with a [Contributor Code of Conduct](https://github.com/sleepingkingstudios/rspec-sleeping_king_studios/blob/master/CODE_OF_CONDUCT.md). By contributing to this project, you agree to abide by its terms.
|
36
52
|
|
37
|
-
|
38
53
|
## Configuration
|
39
54
|
|
40
55
|
RSpec::SleepingKingStudios now has configuration options available through `RSpec.configuration`. For example, to set the behavior of the matcher examples when a failure message expectation is undefined (see RSpec Matcher Examples, below), put the following in your `spec_helper` or other configuration file:
|
@@ -302,6 +317,44 @@ A simplified syntax for re-using shared context or examples without having to ex
|
|
302
317
|
|
303
318
|
(also `::xinclude_context`) A shortcut for skipping the example group by wrapping it in a `describe` block, similar to the built-in `xit` and `xdescribe` methods.
|
304
319
|
|
320
|
+
### Memoized Helpers
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
require 'rspec/sleepingkingstudios/concerns/memoized_helpers'
|
324
|
+
```
|
325
|
+
|
326
|
+
Methods for defining memoized helpers, similar to the core RSpec `let` method.
|
327
|
+
|
328
|
+
#### `.let?`
|
329
|
+
|
330
|
+
The `.let?` class method defines a memoized helper if and only if there is not an existing method with the same name defined in the example group. Among other use cases, this allows for defining default values in shared examples.
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
RSpec.describe Rocket do
|
334
|
+
subject(:rocket) { described_class.new(name: 'Imp IV') }
|
335
|
+
|
336
|
+
shared_examples 'should launch the rocket' do
|
337
|
+
let?(:launch_site) { 'KSC' }
|
338
|
+
|
339
|
+
it 'should launch the rocket' do
|
340
|
+
expect { rocket.launch_from(launch_site) }
|
341
|
+
.to change(rocket, :launched?)
|
342
|
+
.to be true
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
describe '#launch_from' do
|
347
|
+
include_examples 'should launch the rocket'
|
348
|
+
|
349
|
+
describe 'with launch_site: value' do
|
350
|
+
let(:launch_site) { 'Baikerbanur' }
|
351
|
+
|
352
|
+
include_examples 'should launch the rocket'
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
```
|
357
|
+
|
305
358
|
### Shared Example Groups
|
306
359
|
|
307
360
|
require 'rspec/sleeping_king_studios/concerns/shared_example_group'
|
@@ -439,7 +492,7 @@ A simplified syntax for re-using shared context or examples without having to ex
|
|
439
492
|
## Contracts
|
440
493
|
|
441
494
|
```ruby
|
442
|
-
require 'rspec/
|
495
|
+
require 'rspec/sleeping_king_studios/contract'
|
443
496
|
```
|
444
497
|
|
445
498
|
An `RSpec::SleepingKingStudios::Contract` object encapsulates a partial RSpec specification. Unlike a traditional shared example group, a contract can be reused across projects, allowing a library to define an interface, provide a reference implementation, and publish tests that validate other implementations.
|
@@ -450,7 +503,9 @@ Contracts can be added to an example group either through the `.apply` method or
|
|
450
503
|
module ExampleContracts
|
451
504
|
# This contract asserts that the object has the Enumerable module as an
|
452
505
|
# ancestor, and that it responds to the #each method.
|
453
|
-
class ShouldBeEnumerableContract
|
506
|
+
class ShouldBeEnumerableContract
|
507
|
+
extend RSpec::SleepingKingStudios::Contract
|
508
|
+
|
454
509
|
# @!method apply(example_group)
|
455
510
|
# Adds the contract to the example group.
|
456
511
|
|
@@ -786,7 +841,7 @@ When the value does not match the expectation, the failure message will provide
|
|
786
841
|
# expected: == {:body=>{:order=>{:id=>an instance of Integer, :total=>"9.99"}}, :status=>200}
|
787
842
|
# got: {:status=>400, :body=>{:order=>{:fulfilled=>false, :total=>"19.99"}}, :errors=>["Insufficient funds"]}
|
788
843
|
#
|
789
|
-
# (compared using
|
844
|
+
# (compared using Hashdiff)
|
790
845
|
#
|
791
846
|
# Diff:
|
792
847
|
# + :body.:order.:fulfilled => got false
|
@@ -961,6 +1016,170 @@ Creates a value spy that watches the value of a method call or block. The spy al
|
|
961
1016
|
|
962
1017
|
**Chaining:** None.
|
963
1018
|
|
1019
|
+
## Sandbox
|
1020
|
+
|
1021
|
+
The `RSpec::SleepingKingStudios::Sandbox` module allows for running a spec file or files in an isolated environment and capturing the results. This can be useful for testing code meant to enhance your tests, such as a custom RSpec matcher or a shared example group.
|
1022
|
+
|
1023
|
+
First, define a spec file to run. As a recommended convention, spec files to be run in a sandbox should be given the `spec.fixture.rb` suffix to ensure they are not accidentally run with the main test suite.
|
1024
|
+
|
1025
|
+
```ruby
|
1026
|
+
# frozen_string_literal: true
|
1027
|
+
|
1028
|
+
# In spec/rocket_spec.fixture.rb:
|
1029
|
+
RSpec.describe Rocket do
|
1030
|
+
describe '#launch' do
|
1031
|
+
it 'should launch the rocket' do
|
1032
|
+
expect { subject.launch }.to change(subject, :launched?).to be true
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
```
|
1037
|
+
|
1038
|
+
Defining fixture files rather than generating temporary files is recommended for performance reasons, but both approaches are possible. Once the file is defined, it can be run in a sandbox:
|
1039
|
+
|
1040
|
+
```ruby
|
1041
|
+
result = RSpec::SleepingKingStudios::Sandbox.run('spec/rocket_spec.fixture.rb')
|
1042
|
+
|
1043
|
+
result.class #=> RSpec::SleepingKingStudios::Sandbox::Result
|
1044
|
+
result.output #=> """
|
1045
|
+
# Rocket
|
1046
|
+
# #launch
|
1047
|
+
# should launch the rocket
|
1048
|
+
#
|
1049
|
+
# 1 example, 0 failures
|
1050
|
+
# """
|
1051
|
+
result.status #=> 1
|
1052
|
+
result.summary #=> 1 example, 0 failures
|
1053
|
+
result.example_descriptions #=> [
|
1054
|
+
# 'Rocket#launch should launch the rocket'
|
1055
|
+
# ]
|
1056
|
+
```
|
1057
|
+
|
1058
|
+
The `.run` method returns an instance of `RSpec::SleepingKingStudios::Sandbox::Result`, which wraps the result of running the specified spec files. It defines the following methods:
|
1059
|
+
|
1060
|
+
- `#errors`: The output captured from STDERR when running the files.
|
1061
|
+
- `#example_descriptions`: The full description for each evaluated example.
|
1062
|
+
- `#json`: The json output from running the files.
|
1063
|
+
- `#output`: The output captured from STDOUT when running the files. The specs are run with the `--format=doc` flag, so this will include the individual examples as well as the summary.
|
1064
|
+
- `#summary`: The summary line for the tests.
|
1065
|
+
|
1066
|
+
## Deferred Examples
|
1067
|
+
|
1068
|
+
`RSpec::SleepingKingStudios::Deferred` provides a mechanism for defining specifications that can be reused and shared between projects. For example, a library could use deferred examples to define an interface and test a reference implementation; projects that use that library could then use the published deferred examples to validate their own implementations of that interface.
|
1069
|
+
|
1070
|
+
At its core, a deferred example group is a Ruby `module`, and hooks into the inheritance hierarchy when `include`d into an example group. This gives certain advantages relative to traditional shared examples (and contracts):
|
1071
|
+
|
1072
|
+
- Deferred examples are defined in a specific context, and can be shared by simply `include`-ing the containing module (or the deferred examples directly). There is no global namespace, and using deferred examples (even from other projects) is as simple as an `include` call.
|
1073
|
+
- Deferred examples stack, rather than conflict. This means helper methods and memoized helpers can reference other included examples using `super()`, rather than simply overwriting other helpers at the same "level".
|
1074
|
+
- Unlike shared example groups, deferred examples defined via a `Provider` (see [Parameterized Examples](#parameterized-examples), below) can accept a block parameter.
|
1075
|
+
|
1076
|
+
```ruby
|
1077
|
+
module ShouldBeAVehicleExamples
|
1078
|
+
include RSpec::SleepingKingStudios::Deferred::Examples
|
1079
|
+
|
1080
|
+
describe '#type' do
|
1081
|
+
it { expect(subject).to respond_to(:type).with(0).arguments }
|
1082
|
+
|
1083
|
+
it { expect(subject.type).to be == expected_type }
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
RSpec.describe Rocket do
|
1088
|
+
include ShouldBeAVehicleExamples
|
1089
|
+
|
1090
|
+
subject(:rocket) { described_class.new }
|
1091
|
+
|
1092
|
+
let(:expected_type) { :rocket }
|
1093
|
+
|
1094
|
+
describe '#launch' do
|
1095
|
+
it { expect(rocket).to respond_to(:launch) }
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
```
|
1099
|
+
|
1100
|
+
### Parameterized Examples
|
1101
|
+
|
1102
|
+
Deferred examples can also be defined with parameters using the `Deferred::Provider` DSL. Defining a parameterized example group allows for defining and sharing specs that describe complex and conditional behavior.
|
1103
|
+
|
1104
|
+
For deferred specs that set up a context rather than examples, `deferred_context` is provided as an alias to `Provider.deferred_examples`.
|
1105
|
+
|
1106
|
+
```ruby
|
1107
|
+
module VehicleExamples
|
1108
|
+
include RSpec::SleepingKingStudios::Deferred::Provider
|
1109
|
+
|
1110
|
+
deferred_context 'when the vehicle needs to be serviced' \
|
1111
|
+
do |serviced_at: '2020-01-01'|
|
1112
|
+
before(:example) do
|
1113
|
+
vehicle.last_serviced_at = serviced_at
|
1114
|
+
end
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
deferred_examples 'should be a Vehicle' do |vehicle_type:|
|
1118
|
+
it { expect(subject).to be_a Spec::Models::Vehicle }
|
1119
|
+
|
1120
|
+
describe '#type' do
|
1121
|
+
it { expect(subject.type).to be == vehicle_type }
|
1122
|
+
end
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
```
|
1126
|
+
|
1127
|
+
The deferred examples can be included in example groups using the `Deferred::Consumer` DSL.
|
1128
|
+
|
1129
|
+
```ruby
|
1130
|
+
RSpec.describe Car do
|
1131
|
+
include RSpec::SleepingKingStudios::Deferred::Consumer
|
1132
|
+
include VehicleExamples
|
1133
|
+
|
1134
|
+
subject(:car) { described_class.new }
|
1135
|
+
|
1136
|
+
include_deferred 'should be a Vehicle', vehicle_type: :car
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
RSpec.describe Rocket do
|
1140
|
+
include RSpec::SleepingKingStudios::Deferred::Consumer
|
1141
|
+
include VehicleExamples
|
1142
|
+
|
1143
|
+
subject(:rocket) { described_class.new }
|
1144
|
+
|
1145
|
+
include_deferred 'should be a Vehicle', vehicle_type: :rocket
|
1146
|
+
end
|
1147
|
+
```
|
1148
|
+
|
1149
|
+
`Deferred::Consumer` also defines support for wrapped deferred examples, which automatically generate a new context and include the deferred examples in the new example group. If `#wrap_deferred` is passed a block, that block will automatically be evaluated in the context of the example group, allowing you to define additional context or examples.
|
1150
|
+
|
1151
|
+
```ruby
|
1152
|
+
RSpec.describe Car do
|
1153
|
+
include RSpec::SleepingKingStudios::Deferred::Consumer
|
1154
|
+
include VehicleExamples
|
1155
|
+
|
1156
|
+
subject(:car) { described_class.new }
|
1157
|
+
|
1158
|
+
wrap_deferred 'when the vehicle needs to be serviced' do
|
1159
|
+
it { expect(car.last_serviced_at).to be == '2020-01-01' }
|
1160
|
+
end
|
1161
|
+
end
|
1162
|
+
```
|
1163
|
+
|
1164
|
+
`Deferred::Consumer` also includes the shortcuts `#finclude_deferred` and `#fwrap_deferred` to automatically focus deferred examples, or `#xinclude_deferred` and `#xwrap_deferred` to skip deferred examples.
|
1165
|
+
|
1166
|
+
The `defined_deferred_examples?` and `defined_deferred_context?` methods allow checking for the presence of a deferred example group.
|
1167
|
+
|
1168
|
+
```ruby
|
1169
|
+
RSpec.describe Boat do
|
1170
|
+
include RSpec::SleepingKingStudios::Deferred::Consumer
|
1171
|
+
include VehicleExamples
|
1172
|
+
|
1173
|
+
subject(:boat) { described_class.new }
|
1174
|
+
|
1175
|
+
if defined_deferred_examples?('should be a water Vehicle')
|
1176
|
+
include_deferred 'should be a water Vehicle'
|
1177
|
+
else
|
1178
|
+
pending 'deferred examples "should be a water Vehicle" is not defined'
|
1179
|
+
end
|
1180
|
+
end
|
1181
|
+
```
|
1182
|
+
|
964
1183
|
## Shared Examples
|
965
1184
|
|
966
1185
|
To use a custom example group, `require` the associated file and then `include`
|
@@ -0,0 +1,41 @@
|
|
1
|
+
RSpec:
|
2
|
+
Language:
|
3
|
+
ExampleGroups:
|
4
|
+
Regular:
|
5
|
+
- context
|
6
|
+
- describe
|
7
|
+
- wrap_context
|
8
|
+
- wrap_deferred
|
9
|
+
Skipped:
|
10
|
+
- xcontext
|
11
|
+
- xdescribe
|
12
|
+
- xwrap_context
|
13
|
+
- xwrap_deferred
|
14
|
+
Focused:
|
15
|
+
- fcontext
|
16
|
+
- fdescribe
|
17
|
+
- fwrap_context
|
18
|
+
- fwrap_deferred
|
19
|
+
Helpers:
|
20
|
+
- let
|
21
|
+
- let!
|
22
|
+
- let?
|
23
|
+
Includes:
|
24
|
+
Examples:
|
25
|
+
- finclude_contract
|
26
|
+
- finclude_deferred
|
27
|
+
- finclude_examples
|
28
|
+
- fwrap_examples
|
29
|
+
- include_contract
|
30
|
+
- include_deferred
|
31
|
+
- include_examples
|
32
|
+
- wrap_examples
|
33
|
+
- xinclude_contract
|
34
|
+
- xinclude_deferred
|
35
|
+
- xinclude_examples
|
36
|
+
- xwrap_examples
|
37
|
+
SharedGroups:
|
38
|
+
Examples:
|
39
|
+
- deferred_examples
|
40
|
+
Context:
|
41
|
+
- deferred_context
|
@@ -1,107 +1,140 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rspec/sleeping_king_studios/concerns'
|
4
4
|
|
5
5
|
module RSpec::SleepingKingStudios::Concerns
|
6
|
-
|
6
|
+
# Methods for defining example-scoped classes and constants.
|
7
|
+
module ExampleConstants # rubocop:disable Metrics/ModuleLength
|
7
8
|
DEFAULT_VALUE = Object.new.freeze
|
8
9
|
private_constant :DEFAULT_VALUE
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
class << self
|
12
|
+
# @api private
|
13
|
+
def define_class(class_name:, example:, base_class: nil, &block)
|
14
|
+
klass = Class.new(resolve_base_class(base_class))
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
klass.define_singleton_method(:name) { class_name }
|
17
|
+
klass.singleton_class.send(:alias_method, :inspect, :name)
|
18
|
+
klass.singleton_class.send(:alias_method, :to_s, :name)
|
16
19
|
|
17
|
-
|
20
|
+
example.instance_exec(klass, &block) if block_given?
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
if prior_value == DEFAULT_VALUE
|
22
|
-
namespace.send :remove_const, constant_name
|
23
|
-
else
|
24
|
-
namespace.const_set(constant_name, prior_value)
|
25
|
-
end # if-else
|
26
|
-
end # class method assign_constant
|
22
|
+
klass
|
23
|
+
end
|
27
24
|
|
28
|
-
|
29
|
-
|
25
|
+
# @api private
|
26
|
+
def with_constant( # rubocop:disable Metrics/MethodLength
|
27
|
+
constant_name:,
|
28
|
+
constant_value:,
|
29
|
+
namespace:,
|
30
|
+
force: false
|
31
|
+
)
|
32
|
+
guard_existing_constant!(namespace, constant_name) unless force
|
30
33
|
|
31
|
-
|
32
|
-
"constant #{constant_name} is already defined with value "\
|
33
|
-
"#{namespace.const_get(constant_name).inspect}"
|
34
|
+
prior_value = DEFAULT_VALUE
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
if namespace.const_defined?(constant_name)
|
37
|
+
prior_value = namespace.const_get(constant_name)
|
38
|
+
end
|
37
39
|
|
38
|
-
|
39
|
-
value = value.fetch(:base_class, nil) if value.is_a?(Hash)
|
40
|
+
namespace.const_set(constant_name, constant_value)
|
40
41
|
|
41
|
-
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
if prior_value == DEFAULT_VALUE
|
45
|
+
namespace.send :remove_const, constant_name
|
46
|
+
else
|
47
|
+
namespace.const_set(constant_name, prior_value)
|
48
|
+
end
|
49
|
+
end
|
42
50
|
|
43
|
-
|
51
|
+
# @api private
|
52
|
+
def with_namespace(module_names) # rubocop:disable Metrics/MethodLength
|
53
|
+
last_defined = nil
|
44
54
|
|
45
|
-
|
46
|
-
|
55
|
+
resolved =
|
56
|
+
module_names.reduce(Object) do |namespace, module_name|
|
57
|
+
if namespace.const_defined?(module_name)
|
58
|
+
next namespace.const_get(module_name)
|
59
|
+
end
|
47
60
|
|
48
|
-
|
49
|
-
last_defined = nil
|
61
|
+
last_defined ||= { namespace:, module_name: }
|
50
62
|
|
51
|
-
|
52
|
-
|
53
|
-
next ns.const_get(module_name) if ns.const_defined?(module_name)
|
63
|
+
namespace.const_set(module_name, Module.new)
|
64
|
+
end
|
54
65
|
|
55
|
-
|
66
|
+
yield resolved
|
67
|
+
ensure
|
68
|
+
if last_defined
|
69
|
+
last_defined[:namespace]
|
70
|
+
.send(:remove_const, last_defined[:module_name])
|
71
|
+
end
|
72
|
+
end
|
56
73
|
|
57
|
-
|
58
|
-
end # reduce
|
74
|
+
private
|
59
75
|
|
60
|
-
|
61
|
-
|
62
|
-
if last_defined
|
63
|
-
last_defined[:namespace].send(:remove_const, last_defined[:module_name])
|
64
|
-
end # if
|
65
|
-
end # class method resolve_namespace
|
76
|
+
def guard_existing_constant!(namespace, constant_name)
|
77
|
+
return unless namespace.const_defined?(constant_name)
|
66
78
|
|
67
|
-
|
68
|
-
|
79
|
+
message =
|
80
|
+
"constant #{constant_name} is already defined with value " \
|
81
|
+
"#{namespace.const_get(constant_name).inspect}"
|
69
82
|
|
70
|
-
|
71
|
-
|
83
|
+
raise NameError, message
|
84
|
+
end
|
72
85
|
|
73
|
-
|
74
|
-
|
75
|
-
klass.singleton_class.send(:alias_method, :to_s, :name)
|
86
|
+
def resolve_base_class(value)
|
87
|
+
value = value.fetch(:base_class, nil) if value.is_a?(Hash)
|
76
88
|
|
77
|
-
|
89
|
+
return Object if value.nil?
|
78
90
|
|
79
|
-
|
80
|
-
end # example_constant
|
81
|
-
end # method example_class
|
91
|
+
return Object.const_get(value) if value.is_a?(String)
|
82
92
|
|
83
|
-
|
84
|
-
|
85
|
-
|
93
|
+
value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def example_class(class_name, base_class = nil, &block)
|
98
|
+
class_name = class_name.to_s if class_name.is_a?(Symbol)
|
99
|
+
|
100
|
+
example_constant(class_name) do
|
101
|
+
ExampleConstants.define_class(
|
102
|
+
base_class:,
|
103
|
+
class_name:,
|
104
|
+
example: self,
|
105
|
+
&block
|
106
|
+
)
|
107
|
+
end
|
108
|
+
end
|
86
109
|
|
110
|
+
def example_constant( # rubocop:disable Metrics/MethodLength
|
111
|
+
qualified_name,
|
112
|
+
constant_value = DEFAULT_VALUE,
|
113
|
+
force: false,
|
114
|
+
&block
|
115
|
+
)
|
116
|
+
around(:example) do |wrapped_example|
|
87
117
|
resolved_value =
|
88
|
-
if constant_value == DEFAULT_VALUE
|
89
|
-
|
118
|
+
if constant_value == DEFAULT_VALUE && block_given?
|
119
|
+
wrapped_example.example.instance_exec(&block)
|
90
120
|
else
|
91
121
|
constant_value
|
92
|
-
end
|
93
|
-
|
94
|
-
module_names
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
122
|
+
end
|
123
|
+
|
124
|
+
*module_names, constant_name = qualified_name.to_s.split('::')
|
125
|
+
|
126
|
+
ExampleConstants.with_namespace(module_names) do |namespace|
|
127
|
+
ExampleConstants.with_constant(
|
128
|
+
constant_name:,
|
129
|
+
constant_value: resolved_value,
|
130
|
+
namespace:,
|
131
|
+
force:
|
132
|
+
) \
|
133
|
+
do
|
101
134
|
wrapped_example.call
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/sleeping_king_studios/concerns'
|
4
|
+
|
5
|
+
module RSpec::SleepingKingStudios::Concerns
|
6
|
+
# Methods for defining memoized helpers in example groups.
|
7
|
+
module MemoizedHelpers
|
8
|
+
# Defines a memoized helper if a method of the same name is not defined.
|
9
|
+
#
|
10
|
+
# @param method_name [String] the name of the helper method.
|
11
|
+
#
|
12
|
+
# @yieldreturn [Object] the value of the memoized helper method.
|
13
|
+
def let?(method_name, &)
|
14
|
+
return method_name if method_defined?(method_name)
|
15
|
+
|
16
|
+
let(method_name, &)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -69,8 +69,11 @@ module RSpec::SleepingKingStudios::Concerns
|
|
69
69
|
# example group by including the module. The shared examples must be
|
70
70
|
# defined before including the module, or they will not be available in the
|
71
71
|
# example group.
|
72
|
-
def shared_examples
|
73
|
-
RSpec
|
72
|
+
def shared_examples(name, *metadata_args, **metadata_kwargs, &block)
|
73
|
+
RSpec
|
74
|
+
.world
|
75
|
+
.shared_example_group_registry
|
76
|
+
.add(self, name, *metadata_args, **metadata_kwargs, &block)
|
74
77
|
end # method shared_examples
|
75
78
|
alias_method :shared_context, :shared_examples
|
76
79
|
|
@@ -1,8 +1,13 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rspec/sleeping_king_studios'
|
4
4
|
|
5
5
|
module RSpec::SleepingKingStudios
|
6
6
|
# RSpec-related concerns to mix into classes or objects.
|
7
|
-
module Concerns
|
8
|
-
|
7
|
+
module Concerns
|
8
|
+
autoload :ExampleConstants,
|
9
|
+
'rspec/sleeping_king_studios/concerns/example_constants'
|
10
|
+
autoload :MemoizedHelpers,
|
11
|
+
'rspec/sleeping_king_studios/concerns/memoized_helpers'
|
12
|
+
end
|
13
|
+
end
|