rubocop-rspec-guide 0.2.1 → 0.4.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/.rubocop.yml +6 -6
- data/.yardopts +9 -0
- data/CHANGELOG.md +92 -0
- data/CONTRIBUTING.md +358 -0
- data/INTEGRATION_TESTING.md +324 -0
- data/README.md +443 -16
- data/Rakefile +49 -0
- data/benchmark/README.md +349 -0
- data/benchmark/baseline_v0.3.1.txt +67 -0
- data/benchmark/baseline_v0.4.0.txt +167 -0
- data/benchmark/benchmark_helper.rb +92 -0
- data/benchmark/compare_versions.rb +136 -0
- data/benchmark/cops_benchmark.rb +428 -0
- data/benchmark/cops_performance.rb +109 -0
- data/benchmark/quick_comparison.rb +58 -0
- data/benchmark/quick_invariant_bench.rb +52 -0
- data/benchmark/rspec_base_integration.rb +86 -0
- data/benchmark/save_baseline.rb +18 -0
- data/benchmark/scalability_benchmark.rb +181 -0
- data/config/default.yml +43 -2
- data/config/obsoletion.yml +6 -0
- data/lib/rubocop/cop/factory_bot_guide/dynamic_attribute_evaluation.rb +193 -0
- data/lib/rubocop/cop/factory_bot_guide/dynamic_attributes_for_time_and_random.rb +10 -106
- data/lib/rubocop/cop/rspec_guide/characteristics_and_contexts.rb +13 -78
- data/lib/rubocop/cop/rspec_guide/context_setup.rb +81 -30
- data/lib/rubocop/cop/rspec_guide/duplicate_before_hooks.rb +89 -22
- data/lib/rubocop/cop/rspec_guide/duplicate_let_values.rb +91 -22
- data/lib/rubocop/cop/rspec_guide/happy_path_first.rb +52 -21
- data/lib/rubocop/cop/rspec_guide/invariant_examples.rb +60 -19
- data/lib/rubocop/cop/rspec_guide/minimum_behavioral_coverage.rb +165 -0
- data/lib/rubocop/rspec/guide/inject.rb +26 -0
- data/lib/rubocop/rspec/guide/plugin.rb +45 -0
- data/lib/rubocop/rspec/guide/version.rb +1 -1
- data/lib/rubocop-rspec-guide.rb +4 -0
- metadata +49 -1
data/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# RuboCop RSpec Guide
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/rubocop-rspec-guide)
|
|
4
|
+
[](https://github.com/rspec-guide/rubocop-rspec-guide/actions)
|
|
5
|
+
[](https://rubygems.org/gems/rubocop-rspec-guide)
|
|
6
|
+
[](LICENSE.txt)
|
|
7
|
+
[](https://www.ruby-lang.org)
|
|
8
|
+
|
|
3
9
|
Custom RuboCop cops that enforce best practices from the [RSpec Style Guide](https://github.com/AlexeyMatskevich/rspec-guide).
|
|
4
10
|
|
|
5
11
|
## Installation
|
|
@@ -18,28 +24,225 @@ gem install rubocop-rspec-guide
|
|
|
18
24
|
|
|
19
25
|
## Usage
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
### Quick Start
|
|
28
|
+
|
|
29
|
+
1. **Add to Gemfile:**
|
|
30
|
+
```ruby
|
|
31
|
+
group :development, :test do
|
|
32
|
+
gem 'rubocop-rspec-guide', require: false
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. **Install:**
|
|
37
|
+
```bash
|
|
38
|
+
bundle install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
3. **Configure `.rubocop.yml`:**
|
|
42
|
+
|
|
43
|
+
**Minimal configuration (v0.4.0+):**
|
|
44
|
+
```yaml
|
|
45
|
+
# RuboCop 1.72+
|
|
46
|
+
plugins:
|
|
47
|
+
- rubocop-rspec-guide
|
|
48
|
+
|
|
49
|
+
# RuboCop < 1.72
|
|
50
|
+
require:
|
|
51
|
+
- rubocop-rspec-guide
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The gem automatically loads its default configuration, including RSpec Language settings.
|
|
55
|
+
|
|
56
|
+
**Optional - explicit config inheritance:**
|
|
57
|
+
|
|
58
|
+
If you want to explicitly inherit the config (not required):
|
|
59
|
+
```yaml
|
|
60
|
+
plugins:
|
|
61
|
+
- rubocop-rspec-guide
|
|
62
|
+
|
|
63
|
+
inherit_gem:
|
|
64
|
+
rubocop-rspec-guide: config/default.yml
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
4. **Run RuboCop:**
|
|
68
|
+
```bash
|
|
69
|
+
bundle exec rubocop
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
5. **Fix offenses automatically (where possible):**
|
|
73
|
+
```bash
|
|
74
|
+
bundle exec rubocop -a
|
|
75
|
+
# or for safe autocorrection only:
|
|
76
|
+
bundle exec rubocop -A
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Autocorrection Support
|
|
80
|
+
|
|
81
|
+
Some cops support **automatic correction** with `rubocop -a`:
|
|
82
|
+
|
|
83
|
+
| Cop | Autocorrect | Safety |
|
|
84
|
+
|-----|-------------|--------|
|
|
85
|
+
| `FactoryBotGuide/DynamicAttributeEvaluation` | ✅ Yes | Safe |
|
|
86
|
+
| `RSpecGuide/MinimumBehavioralCoverage` | ❌ No | - |
|
|
87
|
+
| `RSpecGuide/HappyPathFirst` | ❌ No | - |
|
|
88
|
+
| `RSpecGuide/ContextSetup` | ❌ No | - |
|
|
89
|
+
| `RSpecGuide/DuplicateLetValues` | ❌ No | - |
|
|
90
|
+
| `RSpecGuide/DuplicateBeforeHooks` | ❌ No | - |
|
|
91
|
+
| `RSpecGuide/InvariantExamples` | ❌ No | - |
|
|
92
|
+
|
|
93
|
+
**Example of autocorrection:**
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
# Before: offense detected
|
|
97
|
+
factory :user do
|
|
98
|
+
created_at Time.now
|
|
99
|
+
token SecureRandom.hex
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# After: rubocop -a
|
|
103
|
+
factory :user do
|
|
104
|
+
created_at { Time.now }
|
|
105
|
+
token { SecureRandom.hex }
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Common Patterns
|
|
110
|
+
|
|
111
|
+
#### Pattern 1: Testing Happy Path + Edge Cases
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
# Before (offense)
|
|
115
|
+
describe '#calculate_discount' do
|
|
116
|
+
it 'calculates discount' do
|
|
117
|
+
expect(calculate_discount(100)).to eq(10)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# After (fixed)
|
|
122
|
+
describe '#calculate_discount' do
|
|
123
|
+
context 'with standard price' do
|
|
124
|
+
it { expect(calculate_discount(100)).to eq(10) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
context 'with zero price' do
|
|
128
|
+
it { expect(calculate_discount(0)).to eq(0) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
context 'with negative price' do
|
|
132
|
+
it { expect { calculate_discount(-10) }.to raise_error(ArgumentError) }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Pattern 2: It-blocks + Context-blocks
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
# Good - default behavior + edge cases
|
|
141
|
+
describe '#process_payment' do
|
|
142
|
+
it 'processes payment successfully' do
|
|
143
|
+
expect(process_payment).to be_success
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
context 'when payment gateway is down' do
|
|
147
|
+
before { stub_gateway_down }
|
|
148
|
+
it { expect(process_payment).to be_failure }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
context 'with insufficient funds' do
|
|
152
|
+
let(:balance) { 0 }
|
|
153
|
+
it { expect(process_payment).to be_declined }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### Pattern 3: Extracting Duplicate Setup
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# Before (offense - duplicate let in all contexts)
|
|
162
|
+
describe 'PaymentProcessor' do
|
|
163
|
+
context 'with credit card' do
|
|
164
|
+
let(:currency) { :usd }
|
|
165
|
+
it { expect(process).to be_success }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
context 'with paypal' do
|
|
169
|
+
let(:currency) { :usd } # Duplicate!
|
|
170
|
+
it { expect(process).to be_success }
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# After (fixed - extracted to parent)
|
|
175
|
+
describe 'PaymentProcessor' do
|
|
176
|
+
let(:currency) { :usd } # Moved to parent
|
|
177
|
+
|
|
178
|
+
context 'with credit card' do
|
|
179
|
+
it { expect(process).to be_success }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
context 'with paypal' do
|
|
183
|
+
it { expect(process).to be_success }
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Configuration Examples
|
|
189
|
+
|
|
190
|
+
#### Complete Setup (with rubocop-rspec and rubocop-factory_bot)
|
|
191
|
+
|
|
192
|
+
Most projects use `rubocop-rspec-guide` alongside `rubocop-rspec` and `rubocop-factory_bot`. Here's a complete configuration:
|
|
22
193
|
|
|
23
194
|
```yaml
|
|
195
|
+
# .rubocop.yml
|
|
196
|
+
|
|
197
|
+
# Load all RSpec-related extensions
|
|
24
198
|
require:
|
|
199
|
+
- rubocop-rspec
|
|
200
|
+
- rubocop-rspec_rails # If using Rails
|
|
201
|
+
- rubocop-factory_bot
|
|
25
202
|
- rubocop-rspec-guide
|
|
26
203
|
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
204
|
+
# Or use plugins syntax (RuboCop 1.72+):
|
|
205
|
+
# plugins:
|
|
206
|
+
# - rubocop-rspec
|
|
207
|
+
# - rubocop-rspec_rails
|
|
208
|
+
# - rubocop-factory_bot
|
|
209
|
+
# - rubocop-rspec-guide
|
|
210
|
+
|
|
211
|
+
# RSpec cops (from rubocop-rspec)
|
|
212
|
+
RSpec/VerifiedDoubles:
|
|
213
|
+
Enabled: true
|
|
214
|
+
|
|
215
|
+
RSpec/MessageSpies:
|
|
216
|
+
Enabled: true
|
|
217
|
+
EnforcedStyle: have_received
|
|
218
|
+
|
|
219
|
+
# FactoryBot cops (from rubocop-factory_bot)
|
|
220
|
+
FactoryBot/CreateList:
|
|
221
|
+
Enabled: true
|
|
222
|
+
|
|
223
|
+
# RSpec Style Guide cops (from rubocop-rspec-guide)
|
|
224
|
+
RSpecGuide/MinimumBehavioralCoverage:
|
|
225
|
+
Enabled: true
|
|
226
|
+
|
|
227
|
+
RSpecGuide/HappyPathFirst:
|
|
228
|
+
Enabled: true
|
|
30
229
|
|
|
31
|
-
|
|
32
|
-
|
|
230
|
+
RSpecGuide/ContextSetup:
|
|
231
|
+
Enabled: true
|
|
232
|
+
|
|
233
|
+
FactoryBotGuide/DynamicAttributeEvaluation:
|
|
33
234
|
Enabled: true
|
|
34
235
|
```
|
|
35
236
|
|
|
36
|
-
|
|
237
|
+
**Note:** The gem automatically injects RSpec Language configuration (v0.4.0+), so no `inherit_gem` is needed.
|
|
238
|
+
|
|
239
|
+
#### Strict Mode (for new projects)
|
|
37
240
|
|
|
38
241
|
```yaml
|
|
39
242
|
require:
|
|
40
243
|
- rubocop-rspec-guide
|
|
41
244
|
|
|
42
|
-
RSpecGuide/
|
|
245
|
+
RSpecGuide/MinimumBehavioralCoverage:
|
|
43
246
|
Enabled: true
|
|
44
247
|
|
|
45
248
|
RSpecGuide/HappyPathFirst:
|
|
@@ -50,23 +253,200 @@ RSpecGuide/ContextSetup:
|
|
|
50
253
|
|
|
51
254
|
RSpecGuide/DuplicateLetValues:
|
|
52
255
|
Enabled: true
|
|
256
|
+
WarnOnPartialDuplicates: true
|
|
53
257
|
|
|
54
258
|
RSpecGuide/DuplicateBeforeHooks:
|
|
55
259
|
Enabled: true
|
|
260
|
+
WarnOnPartialDuplicates: true
|
|
56
261
|
|
|
57
262
|
RSpecGuide/InvariantExamples:
|
|
58
263
|
Enabled: true
|
|
59
264
|
MinLeafContexts: 3
|
|
60
265
|
|
|
266
|
+
FactoryBotGuide/DynamicAttributeEvaluation:
|
|
267
|
+
Enabled: true
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### Relaxed Mode (for legacy projects)
|
|
271
|
+
|
|
272
|
+
```yaml
|
|
273
|
+
require:
|
|
274
|
+
- rubocop-rspec-guide
|
|
275
|
+
|
|
276
|
+
# Enable only critical cops
|
|
277
|
+
RSpecGuide/MinimumBehavioralCoverage:
|
|
278
|
+
Enabled: true
|
|
279
|
+
|
|
280
|
+
RSpecGuide/ContextSetup:
|
|
281
|
+
Enabled: true
|
|
282
|
+
|
|
283
|
+
# Disable warnings for partial duplicates
|
|
284
|
+
RSpecGuide/DuplicateLetValues:
|
|
285
|
+
Enabled: true
|
|
286
|
+
WarnOnPartialDuplicates: false
|
|
287
|
+
|
|
288
|
+
RSpecGuide/DuplicateBeforeHooks:
|
|
289
|
+
Enabled: true
|
|
290
|
+
WarnOnPartialDuplicates: false
|
|
291
|
+
|
|
292
|
+
# More lenient threshold for invariants
|
|
293
|
+
RSpecGuide/InvariantExamples:
|
|
294
|
+
Enabled: true
|
|
295
|
+
MinLeafContexts: 5 # Only report if in 5+ contexts
|
|
296
|
+
|
|
297
|
+
# Disable strict cops
|
|
298
|
+
RSpecGuide/HappyPathFirst:
|
|
299
|
+
Enabled: false
|
|
300
|
+
|
|
301
|
+
FactoryBotGuide/DynamicAttributeEvaluation:
|
|
302
|
+
Enabled: true
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Troubleshooting
|
|
306
|
+
|
|
307
|
+
#### Issue: Too many offenses in existing codebase
|
|
308
|
+
|
|
309
|
+
**Solution:** Enable cops gradually:
|
|
310
|
+
|
|
311
|
+
```yaml
|
|
312
|
+
# Start with most important cops
|
|
313
|
+
RSpecGuide/ContextSetup:
|
|
314
|
+
Enabled: true
|
|
315
|
+
|
|
316
|
+
FactoryBotGuide/DynamicAttributeEvaluation:
|
|
317
|
+
Enabled: true
|
|
318
|
+
|
|
319
|
+
# Disable others temporarily
|
|
320
|
+
RSpecGuide/MinimumBehavioralCoverage:
|
|
321
|
+
Enabled: false
|
|
322
|
+
|
|
323
|
+
RSpecGuide/DuplicateLetValues:
|
|
324
|
+
Enabled: false
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Then enable one cop at a time, fix offenses, and move to the next.
|
|
328
|
+
|
|
329
|
+
#### Issue: False positives on simple getters
|
|
330
|
+
|
|
331
|
+
**Solution:** Disable cop for specific tests:
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
describe '#name' do # rubocop:disable RSpecGuide/MinimumBehavioralCoverage
|
|
335
|
+
it { expect(subject.name).to eq('test') }
|
|
336
|
+
end
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### Issue: "Duplicate let" warning but values are contextual
|
|
340
|
+
|
|
341
|
+
**Solution:** This usually indicates poor test hierarchy. Refactor:
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
# Before - partial duplicates (2/3 contexts)
|
|
345
|
+
describe 'Converter' do
|
|
346
|
+
context 'scenario A' do
|
|
347
|
+
let(:format) { :json }
|
|
348
|
+
# ...
|
|
349
|
+
end
|
|
350
|
+
context 'scenario B' do
|
|
351
|
+
let(:format) { :json } # Duplicate!
|
|
352
|
+
# ...
|
|
353
|
+
end
|
|
354
|
+
context 'scenario C' do
|
|
355
|
+
let(:format) { :xml } # Different
|
|
356
|
+
# ...
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# After - better hierarchy
|
|
361
|
+
describe 'Converter' do
|
|
362
|
+
context 'with JSON format' do
|
|
363
|
+
let(:format) { :json }
|
|
364
|
+
|
|
365
|
+
context 'scenario A' do
|
|
366
|
+
# ...
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
context 'scenario B' do
|
|
370
|
+
# ...
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
context 'with XML format' do
|
|
375
|
+
let(:format) { :xml }
|
|
376
|
+
|
|
377
|
+
context 'scenario C' do
|
|
378
|
+
# ...
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Migration Guide
|
|
385
|
+
|
|
386
|
+
#### Upgrading to v0.4.0
|
|
387
|
+
|
|
388
|
+
**Configuration changes:**
|
|
389
|
+
|
|
390
|
+
Starting from v0.4.0, the gem automatically injects its default configuration, including RSpec Language settings. You can simplify your `.rubocop.yml`:
|
|
391
|
+
|
|
392
|
+
```yaml
|
|
393
|
+
# Before (v0.3.x) - explicit inheritance required
|
|
394
|
+
plugins:
|
|
395
|
+
- rubocop-rspec-guide
|
|
396
|
+
|
|
397
|
+
inherit_gem:
|
|
398
|
+
rubocop-rspec-guide: config/default.yml
|
|
399
|
+
|
|
400
|
+
# After (v0.4.0+) - automatic config injection
|
|
401
|
+
plugins:
|
|
402
|
+
- rubocop-rspec-guide
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**What changed:**
|
|
406
|
+
- ✅ `let_it_be` and `let_it_be!` are now automatically recognized (from `test-prof` / `rspec-rails`)
|
|
407
|
+
- ✅ All cops now use `RuboCop::Cop::RSpec::Base` for better RSpec DSL detection
|
|
408
|
+
- ✅ Significant performance improvement: `InvariantExamples` is 4.25x faster
|
|
409
|
+
- ✅ More accurate detection of RSpec constructs
|
|
410
|
+
|
|
411
|
+
**No code changes needed** - your existing RSpec tests will work as before, but with better analysis.
|
|
412
|
+
|
|
413
|
+
#### From CharacteristicsAndContexts to MinimumBehavioralCoverage
|
|
414
|
+
|
|
415
|
+
The old name still works as an alias, but you should update your config:
|
|
416
|
+
|
|
417
|
+
```yaml
|
|
418
|
+
# Old (deprecated)
|
|
419
|
+
RSpecGuide/CharacteristicsAndContexts:
|
|
420
|
+
Enabled: true
|
|
421
|
+
|
|
422
|
+
# New (recommended)
|
|
423
|
+
RSpecGuide/MinimumBehavioralCoverage:
|
|
424
|
+
Enabled: true
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
No code changes needed - the cop behavior is the same.
|
|
428
|
+
|
|
429
|
+
#### From DynamicAttributesForTimeAndRandom to DynamicAttributeEvaluation
|
|
430
|
+
|
|
431
|
+
The old name still works as an alias:
|
|
432
|
+
|
|
433
|
+
```yaml
|
|
434
|
+
# Old (deprecated)
|
|
61
435
|
FactoryBotGuide/DynamicAttributesForTimeAndRandom:
|
|
62
436
|
Enabled: true
|
|
437
|
+
|
|
438
|
+
# New (recommended)
|
|
439
|
+
FactoryBotGuide/DynamicAttributeEvaluation:
|
|
440
|
+
Enabled: true
|
|
63
441
|
```
|
|
64
442
|
|
|
443
|
+
The new cop checks ALL method calls, not just Time/Random, providing better coverage.
|
|
444
|
+
|
|
65
445
|
## Cops
|
|
66
446
|
|
|
67
|
-
### RSpecGuide/
|
|
447
|
+
### RSpecGuide/MinimumBehavioralCoverage
|
|
68
448
|
|
|
69
|
-
Requires at least 2
|
|
449
|
+
Requires at least 2 behavioral variations in a describe block: either 2+ sibling contexts OR it-blocks + context-blocks.
|
|
70
450
|
|
|
71
451
|
```ruby
|
|
72
452
|
# bad
|
|
@@ -74,7 +454,7 @@ describe '#calculate' do
|
|
|
74
454
|
it 'works' { expect(result).to eq(100) }
|
|
75
455
|
end
|
|
76
456
|
|
|
77
|
-
# good
|
|
457
|
+
# good - 2+ contexts
|
|
78
458
|
describe '#calculate' do
|
|
79
459
|
context 'with valid data' do
|
|
80
460
|
it { expect(result).to eq(100) }
|
|
@@ -84,8 +464,21 @@ describe '#calculate' do
|
|
|
84
464
|
it { expect(result).to be_error }
|
|
85
465
|
end
|
|
86
466
|
end
|
|
467
|
+
|
|
468
|
+
# good - it-blocks + context-blocks
|
|
469
|
+
describe '#calculate' do
|
|
470
|
+
it 'works with defaults' do
|
|
471
|
+
expect(result).to eq(100)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
context 'with invalid data' do
|
|
475
|
+
it { expect(result).to be_error }
|
|
476
|
+
end
|
|
477
|
+
end
|
|
87
478
|
```
|
|
88
479
|
|
|
480
|
+
**Note:** The old name `RSpecGuide/CharacteristicsAndContexts` is deprecated but still works as an alias.
|
|
481
|
+
|
|
89
482
|
### RSpecGuide/HappyPathFirst
|
|
90
483
|
|
|
91
484
|
Ensures corner cases are not placed before happy paths.
|
|
@@ -255,24 +648,44 @@ context 'A' do
|
|
|
255
648
|
end
|
|
256
649
|
```
|
|
257
650
|
|
|
258
|
-
### FactoryBotGuide/
|
|
651
|
+
### FactoryBotGuide/DynamicAttributeEvaluation
|
|
259
652
|
|
|
260
|
-
Ensures
|
|
653
|
+
Ensures method calls in factory attributes are wrapped in blocks for dynamic evaluation.
|
|
261
654
|
|
|
262
655
|
```ruby
|
|
263
|
-
# bad
|
|
656
|
+
# bad - method calls evaluated once at factory load time
|
|
264
657
|
factory :user do
|
|
265
|
-
created_at Time.now #
|
|
658
|
+
created_at Time.now # same timestamp for all users!
|
|
266
659
|
token SecureRandom.hex # same token for all users!
|
|
660
|
+
expires_at 1.day.from_now # same expiry for all users!
|
|
661
|
+
tags Array.new # same array instance shared!
|
|
267
662
|
end
|
|
268
663
|
|
|
269
|
-
# good
|
|
664
|
+
# good - wrapped in blocks for dynamic evaluation
|
|
270
665
|
factory :user do
|
|
271
666
|
created_at { Time.now }
|
|
272
667
|
token { SecureRandom.hex }
|
|
668
|
+
expires_at { 1.day.from_now }
|
|
669
|
+
tags { Array.new }
|
|
670
|
+
name "John" # static values are OK
|
|
273
671
|
end
|
|
274
672
|
```
|
|
275
673
|
|
|
674
|
+
**Note:** The old name `FactoryBotGuide/DynamicAttributesForTimeAndRandom` is deprecated but still works as an alias.
|
|
675
|
+
|
|
676
|
+
## Documentation
|
|
677
|
+
|
|
678
|
+
Full API documentation is available:
|
|
679
|
+
|
|
680
|
+
- **Generate locally**: `bundle exec rake doc`
|
|
681
|
+
- **View documentation**: Open `doc/index.html` in your browser
|
|
682
|
+
- **Quick open**: `bundle exec rake doc_open`
|
|
683
|
+
|
|
684
|
+
The documentation includes:
|
|
685
|
+
- Detailed cop descriptions with examples
|
|
686
|
+
- Configuration options for each cop
|
|
687
|
+
- API reference for all classes and modules
|
|
688
|
+
|
|
276
689
|
## Development
|
|
277
690
|
|
|
278
691
|
After checking out the repo:
|
|
@@ -282,10 +695,24 @@ bundle install
|
|
|
282
695
|
bundle exec rspec
|
|
283
696
|
```
|
|
284
697
|
|
|
698
|
+
Generate documentation:
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
bundle exec rake doc
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
Run benchmarks:
|
|
705
|
+
|
|
706
|
+
```bash
|
|
707
|
+
bundle exec rake benchmark:quick
|
|
708
|
+
```
|
|
709
|
+
|
|
285
710
|
## Contributing
|
|
286
711
|
|
|
287
712
|
Bug reports and pull requests are welcome on GitHub.
|
|
288
713
|
|
|
714
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
|
|
715
|
+
|
|
289
716
|
## License
|
|
290
717
|
|
|
291
718
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
|
@@ -8,3 +8,52 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
require "standard/rake"
|
|
9
9
|
|
|
10
10
|
task default: %i[spec standard]
|
|
11
|
+
|
|
12
|
+
namespace :benchmark do
|
|
13
|
+
desc "Run quick benchmarks for all cops (fast feedback, ~1 minute)"
|
|
14
|
+
task :quick do
|
|
15
|
+
ruby "benchmark/cops_benchmark.rb"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc "Run performance benchmarks for all cops"
|
|
19
|
+
task :cops do
|
|
20
|
+
ruby "benchmark/cops_benchmark.rb"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc "Run scalability benchmarks"
|
|
24
|
+
task :scalability do
|
|
25
|
+
ruby "benchmark/scalability_benchmark.rb"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "Run all benchmarks in quick mode (~2 minutes)"
|
|
29
|
+
task all: [:cops, :scalability]
|
|
30
|
+
|
|
31
|
+
desc "Run full benchmarks with accurate measurements (~5 minutes)"
|
|
32
|
+
task :full do
|
|
33
|
+
ENV["FULL_BENCHMARK"] = "1"
|
|
34
|
+
Rake::Task["benchmark:all"].invoke
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "Run all benchmarks in quick mode"
|
|
39
|
+
task benchmark: "benchmark:all"
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
require "yard"
|
|
43
|
+
|
|
44
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
|
45
|
+
t.files = ["lib/**/*.rb"]
|
|
46
|
+
t.options = ["--output-dir", "doc", "--readme", "README.md"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc "Generate documentation and open in browser"
|
|
50
|
+
task doc_open: :doc do
|
|
51
|
+
system("open doc/index.html") || system("xdg-open doc/index.html")
|
|
52
|
+
end
|
|
53
|
+
rescue LoadError
|
|
54
|
+
# YARD not available, skip doc tasks
|
|
55
|
+
desc "Generate YARD documentation (YARD not installed)"
|
|
56
|
+
task :doc do
|
|
57
|
+
abort "YARD is not available. Install it with: gem install yard"
|
|
58
|
+
end
|
|
59
|
+
end
|