rubocop-rspec_parity 1.5.0 → 1.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c9e27c87b24c7df00daa4eb0a88c31644a7f9986b384d5c875d42bb8358d071
4
- data.tar.gz: aaeab2c5bccca24e24c39250299235ec3a46950f48bee5181a575b2cf45ef683
3
+ metadata.gz: 2fc4286fcc69b46b490ae21db9ce9681ca7dd25577521e329d0bc3d89b33940a
4
+ data.tar.gz: da7872a16a143f8d44a178c7b1912996c8d5213fb1538a8f4231e9ceedf361dc
5
5
  SHA512:
6
- metadata.gz: 858ae886ee9a32d83cd0a8695f0e5d0acc8758af31802ef5fdfe3d4686860dc40991adfa29cacb5a24a4191a9d94011e531b35edccbc40b8300b83c2c46b2996
7
- data.tar.gz: 7d1184d3ea750869fcf9d655865e3a1d7b93a3d30f2292bc8e57fbd6cb4166e8da9a8ee274c54ccb9d2f825039911b230ede34731be80f9ade8c98dd3a59e8df
6
+ metadata.gz: 3d9f782158d6c053256401c6b687eb8b22dd1780f444b42783609b428ea0f82898a33e5c502818df86be8e56847a7be8012a99ead9c3aaffe91e5420d7be5a66
7
+ data.tar.gz: 44b7a03a46444bcacbec096e659fd435dc288278ba1f14d72d87311d1bca1b4feb32643c68c6fcb3ff2fbb5be1118adddddb8e4ec164b4d710cc00efbb287bc5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.6.0] - 2026-06-11
4
+
5
+ Fixed: `SufficientContexts` now counts each `it`/`example` within a single context as a separate scenario, so specs that cover branches with multiple examples (instead of separate contexts) no longer trigger false violations
6
+ Updated: `SufficientContexts` violation message now explains that each branch needs one `context` or `it`, and that compound conditions like `a && b` need a scenario per operand
7
+
3
8
  ## [1.5.0] - 2026-05-21
4
9
 
5
10
  Added: `SufficientContexts` now inlines branches from private/protected helpers that are called from exactly one site in the same class (controlled by the new `TraceSingleUsePrivateMethods` config key, default `true`). Violation messages list the helpers whose branches were counted.
@@ -42,8 +42,9 @@ module RuboCop
42
42
  include DepartmentConfig
43
43
  include SpecFileFinder
44
44
 
45
- MSG = "Method `%<method_name>s` has %<branches>d %<branch_word>s but only %<contexts>d %<context_word>s " \
46
- "in spec. Add %<missing>d more %<missing_word>s to cover all branches."
45
+ MSG = "Method `%<method_name>s` has %<branches>d %<branch_word>s but the spec covers only " \
46
+ "%<contexts>d %<scenario_word>s. Add %<missing>d more %<missing_word>s " \
47
+ "(one `context` or `it` per branch; compound conditions like `a && b` need a scenario per operand)."
47
48
 
48
49
  TRACED_SUFFIX = " (including branches from: %<traced>s)"
49
50
 
@@ -59,6 +60,9 @@ module RuboCop
59
60
  /^autosave_/
60
61
  ].freeze
61
62
 
63
+ # Tallies extracted from a spec's text for a single method describe block.
64
+ ParsedSpec = Struct.new(:context_count, :example_count, :has_examples, :has_direct_examples)
65
+
62
66
  def initialize(config = nil, options = nil)
63
67
  super
64
68
  @ignore_memoization = cop_config.fetch("IgnoreMemoization", true)
@@ -121,9 +125,9 @@ module RuboCop
121
125
  branches: branches,
122
126
  branch_word: pluralize("branch", branches),
123
127
  contexts: contexts,
124
- context_word: pluralize("context", contexts),
128
+ scenario_word: pluralize("scenario", contexts),
125
129
  missing: missing,
126
- missing_word: pluralize("context", missing))
130
+ missing_word: pluralize("scenario", missing))
127
131
  message += format(TRACED_SUFFIX, traced: traced_methods.join(", ")) if traced_methods.any?
128
132
  message
129
133
  end
@@ -231,21 +235,38 @@ module RuboCop
231
235
 
232
236
  def count_contexts_for_method(spec_content, method_name)
233
237
  method_pattern = Regexp.escape(method_name)
234
- context_count, has_examples, has_direct_examples = parse_spec_content(spec_content, method_pattern)
238
+ result = parse_spec_content(spec_content, method_pattern)
239
+
240
+ scenario_count(
241
+ result.context_count, result.example_count,
242
+ has_examples: result.has_examples, has_direct_examples: result.has_direct_examples
243
+ )
244
+ end
245
+
246
+ # A test scenario is the smaller unit between "a context block" and "an
247
+ # `it`/`example`". A single context whose examples each exercise a branch
248
+ # covers as many scenarios as it has examples, so the scenario count is
249
+ # the larger of the context-based count and the raw example count. Empty
250
+ # placeholder contexts (no examples) still count via the context-based
251
+ # path, so this never under-counts relative to the old behaviour.
252
+ def scenario_count(context_count, example_count, has_examples:, has_direct_examples:)
253
+ context_based =
254
+ if context_count.positive? && has_direct_examples
255
+ context_count + 1
256
+ elsif context_count.zero? && has_examples
257
+ 1
258
+ else
259
+ context_count
260
+ end
235
261
 
236
- if context_count.positive? && has_direct_examples
237
- context_count + 1
238
- elsif context_count.zero? && has_examples
239
- 1
240
- else
241
- context_count
242
- end
262
+ [context_based, example_count].max
243
263
  end
244
264
 
245
265
  # rubocop:disable Metrics/MethodLength
246
266
  def parse_spec_content(spec_content, method_pattern) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
247
267
  in_method_block = false
248
268
  context_count = 0
269
+ example_count = 0
249
270
  has_examples = false
250
271
  has_direct_examples = false
251
272
  base_indent = 0
@@ -269,6 +290,7 @@ module RuboCop
269
290
  context_count += 1
270
291
  elsif nested_example?(line)
271
292
  has_examples = true
293
+ example_count += 1
272
294
  child_indent ||= current_indent
273
295
  has_direct_examples = true if current_indent == child_indent
274
296
  end
@@ -277,7 +299,7 @@ module RuboCop
277
299
  end
278
300
  end
279
301
 
280
- [context_count, has_examples, has_direct_examples]
302
+ ParsedSpec.new(context_count, example_count, has_examples, has_direct_examples)
281
303
  end
282
304
  # rubocop:enable Metrics/MethodLength
283
305
 
@@ -307,7 +329,6 @@ module RuboCop
307
329
 
308
330
  case word
309
331
  when "branch" then "branches"
310
- when "context" then "contexts"
311
332
  else "#{word}s"
312
333
  end
313
334
  end
@@ -447,6 +468,7 @@ module RuboCop
447
468
  # Count contexts/describes and examples at the top level (under class describe)
448
469
  base_indent = lines[describe_line_index].match(/^(\s*)/)[1].length
449
470
  context_count = 0
471
+ example_count = 0
450
472
  has_examples = false
451
473
  has_direct_examples = false
452
474
  child_indent = nil
@@ -462,18 +484,13 @@ module RuboCop
462
484
  context_count += 1
463
485
  elsif line.match?(/^\s*(?:it|example|specify)\s+/)
464
486
  has_examples = true
487
+ example_count += 1
465
488
  child_indent ||= indent
466
489
  has_direct_examples = true if indent == child_indent
467
490
  end
468
491
  end
469
492
 
470
- if context_count.positive? && has_direct_examples
471
- context_count + 1
472
- elsif context_count.zero? && has_examples
473
- 1
474
- else
475
- context_count
476
- end
493
+ scenario_count(context_count, example_count, has_examples:, has_direct_examples:)
477
494
  end
478
495
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
479
496
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module RSpecParity
5
- VERSION = "1.5.0"
5
+ VERSION = "1.6.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rspec_parity
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Povilas Jurcys