rspec_magic 0.1.2
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 +7 -0
- data/.gitignore +26 -0
- data/.rspec +3 -0
- data/.yardopts +7 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +36 -0
- data/MIT-LICENSE +20 -0
- data/README-ru.md +328 -0
- data/README.md +328 -0
- data/Rakefile +15 -0
- data/lib/rspec_magic/config.rb +17 -0
- data/lib/rspec_magic/stable/alias_method.rb +26 -0
- data/lib/rspec_magic/stable/context_when.rb +101 -0
- data/lib/rspec_magic/stable/described_sym.rb +62 -0
- data/lib/rspec_magic/stable/use_letset.rb +95 -0
- data/lib/rspec_magic/stable/use_method_discovery.rb +75 -0
- data/lib/rspec_magic/stable.rb +13 -0
- data/lib/rspec_magic/unstable/include_dir_context.rb +39 -0
- data/lib/rspec_magic/unstable.rb +13 -0
- data/lib/rspec_magic/version.rb +5 -0
- data/lib/rspec_magic.rb +11 -0
- data/rspec_magic.gemspec +17 -0
- data/spec/lib/rspec_magic/alias_method_spec.rb +19 -0
- data/spec/lib/rspec_magic/context_when_spec.rb +45 -0
- data/spec/lib/rspec_magic/described_sym_spec.rb +39 -0
- data/spec/lib/rspec_magic/include_dir_context/README.md +2 -0
- data/spec/lib/rspec_magic/include_dir_context/_context.rb +4 -0
- data/spec/lib/rspec_magic/include_dir_context/app/_context.rb +4 -0
- data/spec/lib/rspec_magic/include_dir_context/app/idc_consumer_spec.rb +15 -0
- data/spec/lib/rspec_magic/include_dir_context/app/models/_context.rb +4 -0
- data/spec/lib/rspec_magic/include_dir_context/app/models/idc_consumer_spec.rb +15 -0
- data/spec/lib/rspec_magic/include_dir_context/idc_consumer_spec.rb +15 -0
- data/spec/lib/rspec_magic/use_letset_spec.rb +134 -0
- data/spec/lib/rspec_magic/use_method_discovery_spec.rb +24 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/self.rb +6 -0
- data/spec/support/simplecov.rb +11 -0
- metadata +80 -0
data/README.md
ADDED
@@ -0,0 +1,328 @@
|
|
1
|
+
|
2
|
+
# A little bit of magic for RSpec tests
|
3
|
+
|
4
|
+
<!-- @import "[TOC]" {cmd="toc" depthFrom=2 depthTo=6 orderedList=false} -->
|
5
|
+
|
6
|
+
<!-- code_chunk_output -->
|
7
|
+
|
8
|
+
- [Overview](#overview)
|
9
|
+
- [Setup](#setup)
|
10
|
+
- [Features](#features)
|
11
|
+
- [`alias_method`](#alias_method)
|
12
|
+
- [`context_when`](#context_when)
|
13
|
+
- [`described_sym`](#described_sym)
|
14
|
+
- [`include_dir_context`](#include_dir_context)
|
15
|
+
- [`use_letset`](#use_letset)
|
16
|
+
- [`use_method_discovery`](#use_method_discovery)
|
17
|
+
- [Details](#details)
|
18
|
+
- [On setup](#on-setup)
|
19
|
+
- [On `context_when`](#on-context_when)
|
20
|
+
- [On `include_dir_context`](#on-include_dir_context)
|
21
|
+
- [Copyright](#copyright)
|
22
|
+
|
23
|
+
<!-- /code_chunk_output -->
|
24
|
+
|
25
|
+
## Overview
|
26
|
+
|
27
|
+
🆎 *Этот текст можно прочитать на русском языке: [README-ru.md](README-ru.md).*
|
28
|
+
|
29
|
+
RSpecMagic is a set of extensions for writing compact and expressive tests.
|
30
|
+
|
31
|
+
## Setup
|
32
|
+
|
33
|
+
> 💡 *It's assumed that you've already set up RSpec in your project.*
|
34
|
+
|
35
|
+
Add to your project's `Gemfile`:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
gem "rspec_magic"
|
39
|
+
#gem "rspec_magic", git: "https://github.com/dadooda/rspec_magic"
|
40
|
+
```
|
41
|
+
|
42
|
+
Add to your RSpec startup file (usually `spec/spec_helper.rb`):
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
require "rspec_magic/stable"
|
46
|
+
require "rspec_magic/unstable"
|
47
|
+
|
48
|
+
RSpecMagic::Config.spec_path = File.expand_path(".", __dir__)
|
49
|
+
```
|
50
|
+
|
51
|
+
The `spec_path` is used by some of the features, notably, [include_dir_context](#include_dir_context).
|
52
|
+
The computed path should point to `spec/` of the project's directory.
|
53
|
+
|
54
|
+
See [Details](#on-setup).
|
55
|
+
|
56
|
+
## Features
|
57
|
+
|
58
|
+
### `alias_method`
|
59
|
+
|
60
|
+
A matcher to check that a method is an alias of another method.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
describe User do
|
64
|
+
it { is_expected.to alias_method(:admin?, :is_admin) }
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
### `context_when`
|
69
|
+
|
70
|
+
Create a self-descriptive `"when …"` context with one or more `let` variables defined.
|
71
|
+
The blocks below are synonymous.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
context_when name: "Joe", age: 25 do
|
75
|
+
it do
|
76
|
+
expect([name, age]).to eq ["Joe", 25]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
context "when { name: \"Joe\", age: 25 }" do
|
83
|
+
let(:name) { "Joe" }
|
84
|
+
let(:age) { 25 }
|
85
|
+
it do
|
86
|
+
expect([name, age]).to eq ["Joe", 25]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
See [Details](#on-context_when).
|
92
|
+
|
93
|
+
### `described_sym`
|
94
|
+
|
95
|
+
Transform `described_class` into underscored symbols `described_sym` and `me`.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
describe UserProfile do
|
99
|
+
it { expect(described_sym).to eq :user_profile }
|
100
|
+
it { expect(me).to eq :user_profile }
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
Common usage with a factory:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
describe UserProfile do
|
108
|
+
let(:uprof1) { create described_sym }
|
109
|
+
let(:uprof2) { create me }
|
110
|
+
…
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
### `include_dir_context`
|
115
|
+
|
116
|
+
♒︎ *This feature was added recently and may change.*
|
117
|
+
|
118
|
+
Organize shared contexts ([shared_context](https://rspec.info/features/3-12/rspec-core/example-groups/shared-context/)) in a hierarchy.
|
119
|
+
Import relevant shared contexts into the given test.
|
120
|
+
|
121
|
+
Follow these steps:
|
122
|
+
|
123
|
+
1. Make sure that `RSpecMagic::Config.spec_path` is configured correctly.
|
124
|
+
It must point to project's `spec/`.
|
125
|
+
|
126
|
+
2. Across the spec directory tree, create the shared context files, each named `_context.rb`.
|
127
|
+
A typical `_context.rb` looks like this:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
shared_context __dir__ do
|
131
|
+
…
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
3. Add to your hypothetical `spec_helper.rb`:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# Load the shared contexts hierarchy.
|
139
|
+
Dir[File.expand_path("**/_context.rb", __dir__)].each { |fn| require fn }
|
140
|
+
```
|
141
|
+
|
142
|
+
4. In the given spec file, add a call to `include_dir_context` in the body of the outermost `describe`:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
describe … do
|
146
|
+
include_dir_context __dir__
|
147
|
+
…
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
Say, our spec file is `spec/app/controllers/api/player_controller_spec.rb`.
|
152
|
+
|
153
|
+
The main `describe` will load the contexts from the following files, if any:
|
154
|
+
|
155
|
+
```
|
156
|
+
spec/_context.rb
|
157
|
+
spec/app/_context.rb
|
158
|
+
spec/app/controllers/_context.rb
|
159
|
+
spec/app/controllers/api/_context.rb
|
160
|
+
```
|
161
|
+
|
162
|
+
See [Details](#on-include_dir_context).
|
163
|
+
|
164
|
+
### `use_letset`
|
165
|
+
|
166
|
+
Define a method to create `let` variables, which comprise a `Hash` collection.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
describe do
|
170
|
+
# Method is `let_a`. Collection is `attrs`.
|
171
|
+
use_letset :let_a, :attrs
|
172
|
+
|
173
|
+
# Declare `attrs` elements.
|
174
|
+
let_a(:age)
|
175
|
+
let_a(:name)
|
176
|
+
|
177
|
+
subject { attrs }
|
178
|
+
|
179
|
+
# None of the elements is set yet.
|
180
|
+
it { is_expected.to eq({}) }
|
181
|
+
|
182
|
+
# Set `name` and see it in the collection.
|
183
|
+
context_when name: "Joe" do
|
184
|
+
it { is_expected.to eq(name: "Joe") }
|
185
|
+
|
186
|
+
# Add `age` and see both in the collection.
|
187
|
+
context_when age: 25 do
|
188
|
+
it { is_expected.to eq(name: "Joe", age: 25) }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
When used with a block, `let_a` behaves like a regular `let`:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
describe do
|
198
|
+
use_letset :let_a, :attrs
|
199
|
+
|
200
|
+
let_a(:age) { 25 }
|
201
|
+
let_a(:name) { "Joe" }
|
202
|
+
|
203
|
+
it { expect(attrs).to eq(name: "Joe", age: 25) }
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
### `use_method_discovery`
|
208
|
+
|
209
|
+
Create an automatic `let` variable containing the method or action name,
|
210
|
+
computed from the description of the parent `describe`.
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
describe do
|
214
|
+
use_method_discovery :m
|
215
|
+
|
216
|
+
subject { m }
|
217
|
+
|
218
|
+
describe "#first_name" do
|
219
|
+
it { is_expected.to eq :first_name }
|
220
|
+
end
|
221
|
+
|
222
|
+
describe ".some_stuff" do
|
223
|
+
it { is_expected.to eq :some_stuff }
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "GET some_action" do
|
227
|
+
describe "intermediate context" do
|
228
|
+
it { is_expected.to eq :some_action } # (1)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
`m` finds the nearest *usable* context, whose description format allows
|
235
|
+
the extraction of the method name.
|
236
|
+
At the line marked (1) above, `m` ignores the loosely formatted `"intermediate context"`
|
237
|
+
and grabs the data from `"GET some_action"`.
|
238
|
+
|
239
|
+
## Details
|
240
|
+
|
241
|
+
### On setup
|
242
|
+
|
243
|
+
1. `stable` and `unstable` are feature sets.
|
244
|
+
Set `unstable` contains recently added features that may change in the coming versions.
|
245
|
+
|
246
|
+
2. It's possible to include specific features only. Example:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
require "rspec_magic/stable/use_method_discovery"
|
250
|
+
```
|
251
|
+
|
252
|
+
### On `context_when`
|
253
|
+
|
254
|
+
1. The context can be temporarily excluded by prepending `x`:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
xcontext_when … do
|
258
|
+
…
|
259
|
+
end
|
260
|
+
```
|
261
|
+
|
262
|
+
2. It's possible to define a custom report line formatter:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
describe "…" do
|
266
|
+
def self._context_when_formatter(h)
|
267
|
+
"when #{h.to_json}"
|
268
|
+
end
|
269
|
+
|
270
|
+
context_when … do
|
271
|
+
…
|
272
|
+
end
|
273
|
+
end
|
274
|
+
```
|
275
|
+
|
276
|
+
3. `context_when` works nicely with [use_letset](#use_letset),
|
277
|
+
usually to set the attributes of the object being tested.
|
278
|
+
|
279
|
+
4. The values of `let` belong to the `describe` level.
|
280
|
+
If you need values computed at the `it` level, use the traditional `let(…) { … }`
|
281
|
+
inside the context.
|
282
|
+
|
283
|
+
### On `include_dir_context`
|
284
|
+
|
285
|
+
There's a cool thing in RSpec — [shared_context](https://rspec.info/features/3-12/rspec-core/example-groups/shared-context/).
|
286
|
+
The idea is simple: somewhere (in a hypothetical `spec_helper.rb`) fold something reusable in a `shared_context "this and that"`,
|
287
|
+
and then import that stuff via `include_context "this and that"` where we need it.
|
288
|
+
|
289
|
+
We can put anything in `shared_context` — reusable tests, `let` variables, *but most importantly* —
|
290
|
+
methods, both belonging to the `describe` level (`def self.doit`) and the `it` level (`def doit`).
|
291
|
+
|
292
|
+
Sounds like a library, innit?
|
293
|
+
|
294
|
+
There's a pinch of salt though.
|
295
|
+
Existing means of contexts management are very primitive and rely solely on unique global names.
|
296
|
+
|
297
|
+
RSpec doesn't let us organize shared contexts in a hierarchy,
|
298
|
+
and import them automatically into groups of spec files, such as:
|
299
|
+
*all* model tests get context M, *all* controller tests get context C, and *all* at once get context A.
|
300
|
+
|
301
|
+
In order to maintain minimal order, one has to come up with unique names for shared contexts,
|
302
|
+
and list those contexts *in each and every* spec file:
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
describe … do
|
306
|
+
include_context "basic"
|
307
|
+
include_context "controllers"
|
308
|
+
include_context "api_controllers"
|
309
|
+
…
|
310
|
+
end
|
311
|
+
```
|
312
|
+
|
313
|
+
If you see anything like this, give your kudos to the author, as this is an advanced level.
|
314
|
+
Most of the time, people don't even do that, but simply dump all of their extensions
|
315
|
+
into some “helper”-like pile of cr%p, justifying it by stating that “there was no time to sort it out”.
|
316
|
+
|
317
|
+
What does `include_dir_context` have to offer?
|
318
|
+
|
319
|
+
1. The means to organize shared contexts in a hierarchy.
|
320
|
+
2. The means to automatically import sets of *what is needed* into *where it's needed.*
|
321
|
+
|
322
|
+
See the [main chapter](#include_dir_context) for a step-by-step example.
|
323
|
+
|
324
|
+
## Copyright
|
325
|
+
|
326
|
+
The product is free software distributed under the terms of the MIT license.
|
327
|
+
|
328
|
+
— © 2017-2024 Alex Fortuna
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecMagic
|
4
|
+
# The configuration.
|
5
|
+
module Config
|
6
|
+
class << self
|
7
|
+
attr_writer :spec_path
|
8
|
+
|
9
|
+
# The path of where the specs are.
|
10
|
+
# Most commonly, <tt>spec/</tt> of the project's directory.
|
11
|
+
# @return [String]
|
12
|
+
def spec_path
|
13
|
+
@spec_path || raise("`#{__method__}` must be configured")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecMagic; module Stable
|
4
|
+
# A matcher to check that a method is an alias of another method.
|
5
|
+
#
|
6
|
+
# describe User do
|
7
|
+
# it { is_expected.to alias_method(:admin?, :is_admin) }
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
module AliasMethod
|
11
|
+
end
|
12
|
+
|
13
|
+
# NOTE: `RSpec` has an autoloader of its own. Constants might not respond until we touch them.
|
14
|
+
begin; RSpec::Matchers; rescue NameError; end
|
15
|
+
|
16
|
+
# Activate.
|
17
|
+
defined?(RSpec::Matchers) && RSpec::Matchers.respond_to?(:define) and RSpec::Matchers.define(:alias_method) do |new_name, old_name|
|
18
|
+
match do |subject|
|
19
|
+
expect(subject.method(new_name)).to eq subject.method(old_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
description do
|
23
|
+
"have #{new_name.inspect} aliased to #{old_name.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end; end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
"LODoc"
|
4
|
+
|
5
|
+
module RSpecMagic; module Stable
|
6
|
+
# Create a self-descriptive <tt>"when …"</tt> context with one or more +let+ variables defined.
|
7
|
+
# The blocks below are synonymous.
|
8
|
+
#
|
9
|
+
# context_when name: "Joe", age: 25 do
|
10
|
+
# it do
|
11
|
+
# expect([name, age]).to eq ["Joe", 25]
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# context "when { name: \"Joe\", age: 25 }" do
|
16
|
+
# let(:name) { "Joe" }
|
17
|
+
# let(:age) { 25 }
|
18
|
+
# it do
|
19
|
+
# expect([name, age]).to eq ["Joe", 25]
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# = Features
|
24
|
+
#
|
25
|
+
# Prepend +x+ to +context_when+ to exclude it:
|
26
|
+
#
|
27
|
+
# xcontext_when … do
|
28
|
+
# …
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# ---
|
32
|
+
#
|
33
|
+
# Define a custom report line formatter:
|
34
|
+
#
|
35
|
+
# describe "…" do
|
36
|
+
# def self._context_when_formatter(h)
|
37
|
+
# "when #{h.to_json}"
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# context_when … do
|
41
|
+
# …
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
module ContextWhen
|
46
|
+
module Exports
|
47
|
+
# Default formatter for {#context_when}. Defined your custom one at the +describe+ level if needed.
|
48
|
+
# @param [Hash] h
|
49
|
+
# @return [String]
|
50
|
+
def _context_when_formatter(h)
|
51
|
+
# Extract labels for Proc arguments, if any.
|
52
|
+
labels = {}
|
53
|
+
h.each do |k, v|
|
54
|
+
if v.is_a? Proc
|
55
|
+
begin
|
56
|
+
labels[k] = h.fetch(lk = "#{k}_label".to_sym)
|
57
|
+
h.delete(lk)
|
58
|
+
rescue KeyError
|
59
|
+
raise ArgumentError, "`#{k}` is a `Proc`, `#{k}_label` must be given"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
pcs = h.map do |k, v|
|
65
|
+
[
|
66
|
+
k.is_a?(Symbol) ? "#{k}:" : "#{k.inspect} =>",
|
67
|
+
v.is_a?(Proc) ? labels[k] : v.inspect,
|
68
|
+
].join(" ")
|
69
|
+
end
|
70
|
+
|
71
|
+
"when { " + pcs.join(", ") + " }"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create a context.
|
75
|
+
# @param [Hash] h
|
76
|
+
def context_when(h, &block)
|
77
|
+
context _context_when_formatter(h) do
|
78
|
+
h.each do |k, v|
|
79
|
+
if v.is_a? Proc
|
80
|
+
let(k, &v)
|
81
|
+
else
|
82
|
+
# Generic scalar value.
|
83
|
+
let(k) { v }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
class_eval(&block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Create a temporarily excluded context.
|
91
|
+
def xcontext_when(h, &block)
|
92
|
+
xcontext _context_when_formatter(h) { class_eval(&block) }
|
93
|
+
end
|
94
|
+
end # Exports
|
95
|
+
end # module
|
96
|
+
|
97
|
+
# Activate.
|
98
|
+
defined?(RSpec) && RSpec.respond_to?(:configure) and RSpec.configure do |config|
|
99
|
+
config.extend ContextWhen::Exports
|
100
|
+
end
|
101
|
+
end; end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
"LODoc"
|
4
|
+
|
5
|
+
module RSpecMagic; module Stable
|
6
|
+
# Transform +described_class+ into underscored symbols +described_sym+ and +me+.
|
7
|
+
#
|
8
|
+
# describe UserProfile do
|
9
|
+
# it { expect(described_sym).to eq :user_profile }
|
10
|
+
# it { expect(me).to eq :user_profile }
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# With a factory:
|
14
|
+
#
|
15
|
+
# describe UserProfile do
|
16
|
+
# let(:uprof1) { create described_sym }
|
17
|
+
# let(:uprof2) { create me }
|
18
|
+
# …
|
19
|
+
# end
|
20
|
+
module DescribedSym
|
21
|
+
module Exports
|
22
|
+
# A +Symbol+ representation of +described_class+.
|
23
|
+
# @return [Symbol] E.g. +:user_profile+.
|
24
|
+
def described_sym
|
25
|
+
Util.underscore(described_class.to_s).to_sym
|
26
|
+
end
|
27
|
+
alias_method :me, :described_sym
|
28
|
+
end # Exports
|
29
|
+
|
30
|
+
# Utilities.
|
31
|
+
module Util
|
32
|
+
# Generate an underscored, lowercase representation of a camel-cased word.
|
33
|
+
# <tt>"::"</tt>s are converted to <tt>"/"</tt>s.
|
34
|
+
#
|
35
|
+
# underscore("ActiveModel") # => "active_model"
|
36
|
+
# underscore("ActiveModel::Errors") # => "active_model/errors"
|
37
|
+
#
|
38
|
+
# NOTE: This method has been ported from ActiveSupport, mostly as is.
|
39
|
+
#
|
40
|
+
# @param [String] camel_cased_word
|
41
|
+
# @return [Symbol]
|
42
|
+
def self.underscore(camel_cased_word)
|
43
|
+
# Unlike ActiveSupport, we don't support acronyms.
|
44
|
+
acronym_regex = /(?=a)b/
|
45
|
+
|
46
|
+
word = camel_cased_word.to_s.dup
|
47
|
+
word.gsub!(/::/, '/')
|
48
|
+
word.gsub!(/(?:([A-Za-z\d])|^)(#{acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
|
49
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
50
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
51
|
+
word.tr!("-", "_")
|
52
|
+
word.downcase!
|
53
|
+
word
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end # module
|
57
|
+
|
58
|
+
# Activate.
|
59
|
+
defined?(RSpec) && RSpec.respond_to?(:configure) and RSpec.configure do |config|
|
60
|
+
config.include DescribedSym::Exports
|
61
|
+
end
|
62
|
+
end; end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
"LODoc"
|
4
|
+
|
5
|
+
module RSpecMagic; module Stable
|
6
|
+
# Define a method to create +let+ variables, which comprise a +Hash+ collection.
|
7
|
+
#
|
8
|
+
# describe do
|
9
|
+
# # Method is `let_a`. Collection is `attrs`.
|
10
|
+
# use_letset :let_a, :attrs
|
11
|
+
#
|
12
|
+
# # Declare `attrs` elements.
|
13
|
+
# let_a(:age)
|
14
|
+
# let_a(:name)
|
15
|
+
#
|
16
|
+
# subject { attrs }
|
17
|
+
#
|
18
|
+
# # None of the elements is set yet.
|
19
|
+
# it { is_expected.to eq({}) }
|
20
|
+
#
|
21
|
+
# # Set `name` and see it in the collection.
|
22
|
+
# context_when name: "Joe" do
|
23
|
+
# it { is_expected.to eq(name: "Joe") }
|
24
|
+
#
|
25
|
+
# # Add `age` and see both in the collection.
|
26
|
+
# context_when age: 25 do
|
27
|
+
# it { is_expected.to eq(name: "Joe", age: 25) }
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# When used with a block, +let_a+ behaves like a regular +let+:
|
33
|
+
#
|
34
|
+
# describe do
|
35
|
+
# use_letset :let_a, :attrs
|
36
|
+
#
|
37
|
+
# let_a(:age) { 25 }
|
38
|
+
# let_a(:name) { "Joe" }
|
39
|
+
#
|
40
|
+
# it { expect(attrs).to eq(name: "Joe", age: 25) }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
module UseLetset
|
44
|
+
module Exports
|
45
|
+
# Define the collection.
|
46
|
+
# @param let_method [Symbol]
|
47
|
+
# @param collection_let [Symbol]
|
48
|
+
def use_letset(let_method, collection_let)
|
49
|
+
keys_m = "_#{collection_let}_keys".to_sym
|
50
|
+
|
51
|
+
# See "Implementation notes" on failed implementation of "collection only" mode.
|
52
|
+
|
53
|
+
# E.g. "_data_keys" or something.
|
54
|
+
define_singleton_method(keys_m) do
|
55
|
+
if instance_variable_defined?(k = "@#{keys_m}")
|
56
|
+
instance_variable_get(k)
|
57
|
+
else
|
58
|
+
# Start by copying superclass's known vars or default to `[]`.
|
59
|
+
instance_variable_set(k, (superclass.send(keys_m).dup rescue []))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
define_singleton_method let_method, ->(k, &block) do
|
64
|
+
(send(keys_m) << k).uniq!
|
65
|
+
# Create a `let` variable unless it's a declaration call (`let_a(:name)`).
|
66
|
+
let(k, &block) if block
|
67
|
+
end
|
68
|
+
|
69
|
+
define_method(collection_let) do
|
70
|
+
{}.tap do |h|
|
71
|
+
self.class.send(keys_m).each do |k|
|
72
|
+
h[k] = public_send(k) if respond_to?(k)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end # Exports
|
78
|
+
end # module
|
79
|
+
|
80
|
+
# Activate.
|
81
|
+
defined?(RSpec) && RSpec.respond_to?(:configure) and RSpec.configure do |config|
|
82
|
+
config.extend UseLetset::Exports
|
83
|
+
end
|
84
|
+
end; end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Implementation notes:
|
88
|
+
#
|
89
|
+
# * There was once an idea to support `use_letset` in "collection only" mode. Say, `let_a` appends
|
90
|
+
# to `attrs`, but doesn't publish a let variable. This change IS COMPLETELY NOT IN LINE with
|
91
|
+
# RSpec design. Let variables are methods and the collection is built by probing for those
|
92
|
+
# methods. "Collection only" would require a complete redesign. It's easier to implement another
|
93
|
+
# helper method for that, or, even better, do it with straight Ruby right in the test where
|
94
|
+
# needed. The need for "collection only" mode is incredibly rare, say, specific serializer
|
95
|
+
# tests.
|