rspec_magic 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|