smartest 0.3.3.alpha3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46365bf842594d74023e6c47da5b98f28e2f8ad765a035de09a54f5d127db2ee
4
- data.tar.gz: 53134bcd0052d5cd8b495b36a9ba8bb1344c6dcb0626d0547dfa49bf6eff005e
3
+ metadata.gz: 3d2b5aeaf51e6e6878eac74601d634daab2e423cede641216856a79188c28554
4
+ data.tar.gz: 4f90e558f84062585a0a8b3596c91f9507f9fec09430a86c6782ae7a3ba92bb7
5
5
  SHA512:
6
- metadata.gz: a5ffe755697e79babe4e54e52d9a2e2a13aee1ade7306dfa7adffd7d9ea6ca75b7b96825b05c713261f420f35b28e980a85b068a94823109bffc0a54774d864a
7
- data.tar.gz: c030cd6bfcfb44f8273348f99a36a6213c237c9bd580cd49287351b4bf9b70a04af3b6b7889287f12ffbda747dfd7d6a450b9954bf51f8c2bdf5fb71768027ba
6
+ metadata.gz: 687150dcb8a16d475d76b9840fecf63b7705fc0b5e123380d6ffe3fe415a77ed89228bdbc098e66db22d67164f4b43412229e47311be15b6ab09643bf00aa43f
7
+ data.tar.gz: 037f50862bd76bcb86e4511227a8a5f2ce943e37fdce3d823124ff90872efa1a808bb7eee4a2ab9c0985c685baf2db451c304074e6797f88d1c2f370390adea6
data/DEVELOPMENT.md CHANGED
@@ -46,6 +46,7 @@ smartest/
46
46
  expectation_target.rb
47
47
  matchers.rb
48
48
  simple_stub.rb
49
+ constant_stub_helpers.rb
49
50
 
50
51
  runner.rb
51
52
  test_result.rb
@@ -647,6 +648,10 @@ A practical approach:
647
648
  - `exe/smartest`
648
649
  - load files from ARGV
649
650
  - default glob `smartest/**/*_test.rb`
651
+ - print scaffold guidance and exit 1 when the default run is requested before
652
+ `smartest/` exists
653
+ - expand directory path arguments such as `smartest/` to `**/*_test.rb` files
654
+ under that directory
650
655
  - support `path:line` and `path:start-end` filters that run tests whose `test`
651
656
  blocks contain or intersect the lines
652
657
  - add `smartest/` to the load path before loading tests
data/README.md CHANGED
@@ -123,12 +123,34 @@ bundle exec smartest
123
123
  By default, Smartest loads `smartest/**/*_test.rb`, so a separate `test/`
124
124
  directory can remain available for Minitest.
125
125
 
126
+ If `smartest/` does not exist yet and no explicit paths are passed, Smartest
127
+ prints scaffold commands instead of attempting a default test run:
128
+
129
+ ```text
130
+ No smartest/ directory found.
131
+
132
+ To create a Smartest test scaffold:
133
+ bundle exec smartest --init
134
+
135
+ For browser tests:
136
+ bundle exec smartest --init-browser
137
+
138
+ See all commands:
139
+ bundle exec smartest --help
140
+ ```
141
+
126
142
  You can also pass explicit paths:
127
143
 
128
144
  ```bash
145
+ bundle exec smartest smartest/suite1/
129
146
  bundle exec smartest smartest/**/*_test.rb
147
+ bundle exec smartest smartest/user_test.rb
130
148
  ```
131
149
 
150
+ A directory path is expanded to `**/*_test.rb` under that directory, so
151
+ `bundle exec smartest smartest/suite1/` is equivalent to
152
+ `bundle exec smartest smartest/suite1/**/*_test.rb`.
153
+
132
154
  To run tests by line number, append `:line` or `:start-end` to the file path.
133
155
  Smartest runs tests whose `test` blocks contain or intersect the selected lines:
134
156
 
@@ -152,6 +174,18 @@ bundle exec smartest --help
152
174
  bundle exec smartest --version
153
175
  ```
154
176
 
177
+ The help output lists common commands, including default runs, directory runs,
178
+ single-file runs, line-filtered runs, and scaffold generation:
179
+
180
+ ```bash
181
+ bundle exec smartest
182
+ bundle exec smartest smartest/suite1/
183
+ bundle exec smartest smartest/user_test.rb
184
+ bundle exec smartest smartest/user_test.rb:12
185
+ bundle exec smartest --init
186
+ bundle exec smartest --init-browser
187
+ ```
188
+
155
189
  Output resembles:
156
190
 
157
191
  ```text
@@ -637,16 +671,16 @@ end
637
671
  ```
638
672
 
639
673
  Use `simple_stub(Time, :now) { fixed_time }` for singleton methods such as class
640
- methods. Use `simple_stub_const("AppConfig::PAYMENT_PROVIDER", "fake")` for
641
- constants. Constant stubs are process-global; avoid concurrent tests that stub
642
- the same constant.
674
+ methods.
675
+
676
+ Use `with_stub_const("AppConfig::PAYMENT_PROVIDER", "fake") { ... }` for
677
+ constants in test bodies, `around_test`, or `around_suite`. Constant stubs are
678
+ process-global; avoid concurrent tests that stub the same constant.
643
679
 
644
680
  The method stub helpers call `Smartest::SimpleStub` internally, apply the stub,
645
681
  register `cleanup { stub.reset }`, and return the stub object.
646
- `simple_stub_const` records the previous constant value, replaces it, and
647
- restores or removes it during cleanup. These helpers are available inside
648
- `Smartest::Fixture` fixture blocks because they need cleanup to tie the stub
649
- lifetime to the fixture scope.
682
+ `with_stub_const` records the previous constant value, replaces it, yields to
683
+ the block, and restores or removes the constant with `ensure`.
650
684
 
651
685
  `Smartest::SimpleStub#apply` and `#reset` are idempotent in the current Fiber.
652
686
  `apply!` raises
@@ -880,7 +914,7 @@ Smartest currently focuses on a small runner API:
880
914
  - fixture dependencies through keyword arguments
881
915
  - fixture cleanup
882
916
  - suite-scoped fixtures through `suite_fixture`
883
- - fixture-scoped method and constant stubs
917
+ - fixture-scoped method stubs and block-scoped constant stubs
884
918
  - suite hooks with `around_suite`
885
919
  - test hooks with `around_test`
886
920
  - skipped and pending tests through `skip` and `pending`
data/SMARTEST_DESIGN.md CHANGED
@@ -856,7 +856,24 @@ If no paths are given:
856
856
  bundle exec smartest
857
857
  ```
858
858
 
859
- should default to:
859
+ should default to `smartest/**/*_test.rb` when `smartest/` exists. If the
860
+ directory does not exist, the CLI should print scaffold guidance and exit with
861
+ status `1`:
862
+
863
+ ```text
864
+ No smartest/ directory found.
865
+
866
+ To create a Smartest test scaffold:
867
+ bundle exec smartest --init
868
+
869
+ For browser tests:
870
+ bundle exec smartest --init-browser
871
+
872
+ See all commands:
873
+ bundle exec smartest --help
874
+ ```
875
+
876
+ Default paths:
860
877
 
861
878
  ```text
862
879
  smartest/**/*_test.rb
@@ -877,8 +894,8 @@ arguments.files.each { |file| load File.expand_path(file) }
877
894
  exit Smartest::Runner.new(tests: arguments.select_tests(Smartest.suite.tests)).run
878
895
  ```
879
896
 
880
- `Smartest::CLIArguments` should support file paths, shell globs, `path:line`,
881
- and `path:start-end` filters.
897
+ `Smartest::CLIArguments` should support file paths, directory paths, shell
898
+ globs, `path:line`, and `path:start-end` filters.
882
899
 
883
900
  `smartest/autorun` should use `at_exit`.
884
901
 
data/exe/smartest CHANGED
@@ -7,18 +7,51 @@ require "smartest"
7
7
 
8
8
  usage = <<~USAGE
9
9
  Usage:
10
- smartest [--profile N] [paths...]
11
- smartest [--profile N] path/to/test_file.rb:line[-line]
12
- smartest --init
13
- smartest --init-browser
14
- smartest --version
15
- smartest --help
16
-
17
- When no paths are given, Smartest loads smartest/**/*_test.rb.
18
- Smartest prints the 5 slowest tests after the run by default.
19
- Use --profile N to choose how many slowest tests are printed.
10
+ bundle exec smartest [options] [paths...]
11
+
12
+ Common commands:
13
+ bundle exec smartest
14
+ Run tests under smartest/**/*_test.rb
15
+
16
+ bundle exec smartest smartest/suite1/
17
+ Run test files matching smartest/suite1/**/*_test.rb
18
+
19
+ bundle exec smartest smartest/user_test.rb
20
+ Run one test file
21
+
22
+ bundle exec smartest smartest/user_test.rb:12
23
+ Run tests around line 12
24
+
25
+ bundle exec smartest --init
26
+ Generate a basic Smartest scaffold
27
+
28
+ bundle exec smartest --init-browser
29
+ Generate a Playwright browser-test scaffold
30
+
31
+ Options:
32
+ --profile N
33
+ Print the N slowest tests. Defaults to 5.
34
+
35
+ --version, -v
36
+ Print the installed Smartest version.
37
+
38
+ --help, -h
39
+ Print this help.
20
40
  USAGE
21
41
 
42
+ missing_smartest_directory_message = <<~MESSAGE
43
+ No smartest/ directory found.
44
+
45
+ To create a Smartest test scaffold:
46
+ bundle exec smartest --init
47
+
48
+ For browser tests:
49
+ bundle exec smartest --init-browser
50
+
51
+ See all commands:
52
+ bundle exec smartest --help
53
+ MESSAGE
54
+
22
55
  command = :run
23
56
 
24
57
  begin
@@ -45,10 +78,16 @@ begin
45
78
  Smartest.disable_autorun!
46
79
  Smartest.install_dsl!
47
80
  test_load_path = File.expand_path("smartest", Dir.pwd)
48
- $LOAD_PATH.unshift(test_load_path) if Dir.exist?(test_load_path) && !$LOAD_PATH.include?(test_load_path)
49
81
 
50
82
  arguments = Smartest::CLIArguments.new(ARGV)
51
83
 
84
+ if arguments.default_paths? && !Dir.exist?(test_load_path)
85
+ puts missing_smartest_directory_message
86
+ exit 1
87
+ end
88
+
89
+ $LOAD_PATH.unshift(test_load_path) if Dir.exist?(test_load_path) && !$LOAD_PATH.include?(test_load_path)
90
+
52
91
  arguments.files.each do |file|
53
92
  load File.expand_path(file)
54
93
  end
@@ -5,6 +5,7 @@ require "set"
5
5
  module Smartest
6
6
  class CLIArguments
7
7
  DEFAULT_PROFILE_COUNT = 5
8
+ DEFAULT_PATHS = ["smartest/**/*_test.rb"].freeze
8
9
 
9
10
  attr_reader :files, :line_filters, :profile_count
10
11
 
@@ -13,15 +14,25 @@ module Smartest
13
14
  @whole_files = Set.new
14
15
  @line_filters = Hash.new { |hash, key| hash[key] = Set.new }
15
16
  @profile_count = DEFAULT_PROFILE_COUNT
17
+ @default_paths = false
16
18
 
17
19
  paths = extract_options(argv)
18
- parse_paths(paths.empty? ? ["smartest/**/*_test.rb"] : paths)
20
+ if paths.empty?
21
+ @default_paths = true
22
+ paths = DEFAULT_PATHS
23
+ end
24
+
25
+ parse_paths(paths)
19
26
  end
20
27
 
21
28
  def filter_tests?
22
29
  @line_filters.any?
23
30
  end
24
31
 
32
+ def default_paths?
33
+ @default_paths
34
+ end
35
+
25
36
  def select_tests(tests)
26
37
  return tests unless filter_tests?
27
38
 
@@ -64,8 +75,7 @@ module Smartest
64
75
  def parse_paths(paths)
65
76
  paths.each do |argument|
66
77
  pattern, line_filter = split_line_filter(argument)
67
- matches = Dir[pattern]
68
- files = matches.empty? ? [pattern] : matches
78
+ files = expand_path_pattern(pattern)
69
79
 
70
80
  files.each do |file|
71
81
  @files << file
@@ -82,6 +92,19 @@ module Smartest
82
92
  @files.uniq!
83
93
  end
84
94
 
95
+ def expand_path_pattern(pattern)
96
+ matches = Dir[pattern]
97
+ return [pattern] if matches.empty?
98
+
99
+ matches.flat_map do |match|
100
+ if File.directory?(match)
101
+ Dir[File.join(match, "**", "*_test.rb")]
102
+ else
103
+ match
104
+ end
105
+ end
106
+ end
107
+
85
108
  def split_line_filter(argument)
86
109
  match = argument.match(/\A(.+):(\d+)(?:-(\d+))?\z/)
87
110
  return [argument, nil] unless match
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartest
4
+ module ConstantStubHelpers
5
+ private
6
+
7
+ def with_stub_const(constant_path, value)
8
+ raise ArgumentError, "with_stub_const block is required" unless block_given?
9
+
10
+ owner, constant_name = resolve_with_stub_constant(constant_path)
11
+ original_defined = owner.const_defined?(constant_name, false)
12
+ original_value = owner.const_get(constant_name, false) if original_defined
13
+
14
+ owner.__send__(:remove_const, constant_name) if original_defined
15
+ owner.const_set(constant_name, value)
16
+
17
+ yield
18
+ ensure
19
+ if owner && constant_name
20
+ owner.__send__(:remove_const, constant_name) if owner.const_defined?(constant_name, false)
21
+ owner.const_set(constant_name, original_value) if original_defined
22
+ end
23
+ end
24
+
25
+ def resolve_with_stub_constant(constant_path)
26
+ unless constant_path.is_a?(String) || constant_path.is_a?(Symbol)
27
+ raise ArgumentError, "constant path must be a String or Symbol"
28
+ end
29
+
30
+ names = constant_path.to_s.split("::")
31
+ names.shift if names.first == ""
32
+ raise ArgumentError, "constant path must not be empty" if names.empty?
33
+
34
+ names.each do |name|
35
+ raise ArgumentError, "invalid constant path: #{constant_path}" unless name.match?(/\A[A-Z]\w*\z/)
36
+ end
37
+
38
+ constant_name = names.pop.to_sym
39
+ owner = names.reduce(Object) { |namespace, name| namespace.const_get(name, false) }
40
+ raise ArgumentError, "constant owner must be a Module or Class" unless owner.is_a?(Module)
41
+
42
+ [owner, constant_name]
43
+ end
44
+ end
45
+ end
@@ -4,6 +4,7 @@ module Smartest
4
4
  class ExecutionContext
5
5
  include Expectations
6
6
  include Matchers
7
+ include ConstantStubHelpers
7
8
 
8
9
  def initialize(run_state: TestRunState.new)
9
10
  @run_state = run_state
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Smartest
4
4
  class Fixture
5
- RESERVED_CONTEXT_METHODS = %i[skip pending].freeze
5
+ RESERVED_CONTEXT_METHODS = %i[skip pending with_stub_const].freeze
6
6
 
7
7
  class << self
8
8
  def fixture(name, scope: :test, &block)
@@ -73,48 +73,12 @@ module Smartest
73
73
  apply_simple_stub(SimpleStub.new(object.singleton_class, method_name, &block))
74
74
  end
75
75
 
76
- def simple_stub_const(constant_path, value)
77
- owner, constant_name = resolve_simple_stub_constant(constant_path)
78
- original_defined = owner.const_defined?(constant_name, false)
79
- original_value = owner.const_get(constant_name, false) if original_defined
80
-
81
- owner.__send__(:remove_const, constant_name) if original_defined
82
- owner.const_set(constant_name, value)
83
-
84
- cleanup do
85
- owner.__send__(:remove_const, constant_name) if owner.const_defined?(constant_name, false)
86
- owner.const_set(constant_name, original_value) if original_defined
87
- end
88
-
89
- value
90
- end
91
-
92
76
  def apply_simple_stub(stub)
93
77
  stub.apply!
94
78
  cleanup { stub.reset }
95
79
  stub
96
80
  end
97
81
 
98
- def resolve_simple_stub_constant(constant_path)
99
- unless constant_path.is_a?(String) || constant_path.is_a?(Symbol)
100
- raise ArgumentError, "constant path must be a String or Symbol"
101
- end
102
-
103
- names = constant_path.to_s.split("::")
104
- names.shift if names.first == ""
105
- raise ArgumentError, "constant path must not be empty" if names.empty?
106
-
107
- names.each do |name|
108
- raise ArgumentError, "invalid constant path: #{constant_path}" unless name.match?(/\A[A-Z]\w*\z/)
109
- end
110
-
111
- constant_name = names.pop.to_sym
112
- owner = names.reduce(Object) { |namespace, name| namespace.const_get(name, false) }
113
- raise ArgumentError, "constant owner must be a Module or Class" unless owner.is_a?(Module)
114
-
115
- [owner, constant_name]
116
- end
117
-
118
82
  def method_missing(method_name, *args, &block)
119
83
  return super if RESERVED_CONTEXT_METHODS.include?(method_name)
120
84
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Smartest
4
4
  class AroundSuiteContext
5
+ include ConstantStubHelpers
6
+
5
7
  def initialize(suite)
6
8
  @suite = suite
7
9
  end
@@ -30,6 +32,8 @@ module Smartest
30
32
  end
31
33
 
32
34
  class AroundTestContext
35
+ include ConstantStubHelpers
36
+
33
37
  def initialize(test_run, run_state:)
34
38
  @test_run = test_run
35
39
  @run_state = run_state
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Smartest
4
- VERSION = "0.3.3.alpha3"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/smartest.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "smartest/version"
4
4
  require_relative "smartest/errors"
5
5
  require_relative "smartest/simple_stub"
6
+ require_relative "smartest/constant_stub_helpers"
6
7
  require_relative "smartest/parameter_extractor"
7
8
  require_relative "smartest/test_case"
8
9
  require_relative "smartest/test_registry"
@@ -158,64 +158,101 @@ test("simple_stub applies and resets singleton methods from fixture cleanup") do
158
158
  expect(status).to eq(0)
159
159
  end
160
160
 
161
- test("simple_stub_const applies and resets existing constants from fixture cleanup") do
162
- fixture_class = Class.new(Smartest::Fixture) do
163
- fixture :stubbed_provider do
164
- simple_stub_const("SimpleStubSelfTestConfig::PROVIDER", :stubbed_provider)
161
+ test("with_stub_const applies and resets existing constants in test body blocks") do
162
+ result = with_stub_const("SimpleStubSelfTestConfig::PROVIDER", :stubbed_provider) do
163
+ expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:stubbed_provider)
164
+ :block_result
165
+ end
166
+
167
+ expect(result).to eq(:block_result)
168
+ expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:original_provider)
169
+ end
170
+
171
+ test("with_stub_const removes newly defined constants after test body blocks") do
172
+ with_stub_const("SimpleStubSelfTestConfig::MISSING_PROVIDER", :stubbed_missing_provider) do
173
+ expect(SimpleStubSelfTestConfig::MISSING_PROVIDER).to eq(:stubbed_missing_provider)
174
+ end
175
+
176
+ expect(SimpleStubSelfTestConfig.const_defined?(:MISSING_PROVIDER, false)).to eq(false)
177
+ end
178
+
179
+ test("with_stub_const restores constants when the block raises") do
180
+ error = SimpleStubSelfTest.capture_error(RuntimeError) do
181
+ with_stub_const("SimpleStubSelfTestConfig::PROVIDER", :stubbed_provider) do
182
+ expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:stubbed_provider)
183
+ raise "stubbed block failed"
165
184
  end
166
185
  end
167
186
 
187
+ expect(error.message).to eq("stubbed block failed")
188
+ expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:original_provider)
189
+ end
190
+
191
+ test("with_stub_const requires a block") do
192
+ error = SimpleStubSelfTest.capture_error(ArgumentError) do
193
+ with_stub_const("SimpleStubSelfTestConfig::PROVIDER", :stubbed_provider)
194
+ end
195
+
196
+ expect(error.message).to eq("with_stub_const block is required")
197
+ end
198
+
199
+ test("with_stub_const wraps around_test hooks") do
168
200
  suite = Smartest::Suite.new
169
- suite.fixture_classes.add(fixture_class)
201
+ suite.around_test_hooks << proc do |test_run|
202
+ with_stub_const("SimpleStubSelfTestConfig::PROVIDER", :around_test_provider) do
203
+ test_run.run
204
+ end
205
+ end
170
206
  suite.tests.add(
171
207
  SimpleStubSelfTest.test_case(
172
- "uses constant stub fixture",
173
- proc do |stubbed_provider:|
174
- expect(stubbed_provider).to eq(:stubbed_provider)
175
- expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:stubbed_provider)
176
- end
208
+ "uses around_test constant stub",
209
+ proc { expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:around_test_provider) }
177
210
  )
178
211
  )
212
+
213
+ status, = SimpleStubSelfTest.run_suite(suite)
214
+
215
+ expect(status).to eq(0)
216
+ expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:original_provider)
217
+ end
218
+
219
+ test("with_stub_const wraps around_suite hooks") do
220
+ suite = Smartest::Suite.new
221
+ suite.around_suite_hooks << proc do |suite_run|
222
+ with_stub_const("SimpleStubSelfTestConfig::PROVIDER", :around_suite_provider) do
223
+ suite_run.run
224
+ end
225
+ end
179
226
  suite.tests.add(
180
227
  SimpleStubSelfTest.test_case(
181
- "sees restored constant",
182
- proc { expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:original_provider) }
228
+ "uses around_suite constant stub",
229
+ proc { expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:around_suite_provider) }
183
230
  )
184
231
  )
185
232
 
186
233
  status, = SimpleStubSelfTest.run_suite(suite)
187
234
 
188
235
  expect(status).to eq(0)
236
+ expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:original_provider)
189
237
  end
190
238
 
191
- test("simple_stub_const removes newly defined constants from fixture cleanup") do
239
+ test("with_stub_const is not available inside fixture blocks") do
192
240
  fixture_class = Class.new(Smartest::Fixture) do
193
- fixture :stubbed_missing_provider do
194
- simple_stub_const("SimpleStubSelfTestConfig::MISSING_PROVIDER", :stubbed_missing_provider)
241
+ fixture :bad_constant_stub do
242
+ with_stub_const("SimpleStubSelfTestConfig::PROVIDER", :fixture_provider) { :fixture_provider }
195
243
  end
196
244
  end
197
245
 
198
246
  suite = Smartest::Suite.new
199
247
  suite.fixture_classes.add(fixture_class)
200
- suite.tests.add(
201
- SimpleStubSelfTest.test_case(
202
- "uses new constant stub fixture",
203
- proc do |stubbed_missing_provider:|
204
- expect(stubbed_missing_provider).to eq(:stubbed_missing_provider)
205
- expect(SimpleStubSelfTestConfig::MISSING_PROVIDER).to eq(:stubbed_missing_provider)
206
- end
207
- )
208
- )
209
- suite.tests.add(
210
- SimpleStubSelfTest.test_case(
211
- "sees removed constant",
212
- proc { expect(SimpleStubSelfTestConfig.const_defined?(:MISSING_PROVIDER, false)).to eq(false) }
213
- )
214
- )
248
+ suite.tests.add(SimpleStubSelfTest.test_case("uses bad fixture", proc { |bad_constant_stub:| bad_constant_stub }))
215
249
 
216
- status, = SimpleStubSelfTest.run_suite(suite)
250
+ status, output = SimpleStubSelfTest.run_suite(suite)
217
251
 
218
- expect(status).to eq(0)
252
+ expect(status).to eq(1)
253
+ expect(output).to include("NoMethodError")
254
+ expect(output).to include("with_stub_const")
255
+ expect(SimpleStubSelfTestConfig::PROVIDER).to eq(:original_provider)
219
256
  end
220
257
 
221
258
  test("simple stub preserves receiver self and method blocks") do
@@ -1435,6 +1435,44 @@ test("cli loads files and returns failure status") do
1435
1435
  end
1436
1436
  end
1437
1437
 
1438
+ test("cli expands directory paths to tests under that directory") do
1439
+ Dir.mktmpdir do |dir|
1440
+ smartest_dir = File.join(dir, "smartest")
1441
+ FileUtils.mkdir_p(File.join(smartest_dir, "nested"))
1442
+ File.write(File.join(smartest_dir, "test_helper.rb"), <<~RUBY)
1443
+ require "smartest/autorun"
1444
+ RUBY
1445
+ File.write(File.join(smartest_dir, "root_test.rb"), <<~RUBY)
1446
+ require "test_helper"
1447
+
1448
+ test("root directory test") do
1449
+ expect(1).to eq(1)
1450
+ end
1451
+ RUBY
1452
+ File.write(File.join(smartest_dir, "nested", "nested_test.rb"), <<~RUBY)
1453
+ require "test_helper"
1454
+
1455
+ test("nested directory test") do
1456
+ expect(2).to eq(2)
1457
+ end
1458
+ RUBY
1459
+
1460
+ stdout, stderr, status = Open3.capture3(
1461
+ { "RUBYLIB" => File.expand_path("../lib", __dir__) },
1462
+ "ruby",
1463
+ File.expand_path("../exe/smartest", __dir__),
1464
+ "smartest/",
1465
+ chdir: dir
1466
+ )
1467
+
1468
+ expect(status.success?).to eq(true)
1469
+ expect(stderr).to eq("")
1470
+ expect(stdout).to include("root directory test")
1471
+ expect(stdout).to include("nested directory test")
1472
+ expect(stdout).to include("2 tests, 2 passed, 0 failed")
1473
+ end
1474
+ end
1475
+
1438
1476
  test("cli loads matcher files registered in test helper") do
1439
1477
  Dir.mktmpdir do |dir|
1440
1478
  smartest_dir = File.join(dir, "smartest")
@@ -1656,14 +1694,61 @@ test("cli prints help") do
1656
1694
  expect(status.success?).to eq(true)
1657
1695
  expect(stderr).to eq("")
1658
1696
  expect(stdout).to include("Usage:")
1659
- expect(stdout).to include("smartest [--profile N] [paths...]")
1660
- expect(stdout).to include("smartest [--profile N] path/to/test_file.rb:line[-line]")
1661
- expect(stdout).to include("smartest --init")
1662
- expect(stdout).to include("smartest --init-browser")
1663
- expect(stdout).to include("Use --profile N")
1697
+ expect(stdout).to include("bundle exec smartest [options] [paths...]")
1698
+ expect(stdout).to include("Common commands:")
1699
+ expect(stdout).to include("bundle exec smartest smartest/suite1/")
1700
+ expect(stdout).to include("Run test files matching smartest/suite1/**/*_test.rb")
1701
+ expect(stdout).to include("bundle exec smartest smartest/user_test.rb")
1702
+ expect(stdout).to include("bundle exec smartest smartest/user_test.rb:12")
1703
+ expect(stdout).not_to include("bundle exec smartest smartest/example_browser_test.rb")
1704
+ expect(stdout).not_to include("Run a browser test file")
1705
+ expect(stdout).to include("bundle exec smartest --init")
1706
+ expect(stdout).to include("bundle exec smartest --init-browser")
1707
+ expect(stdout).to include("--profile N")
1664
1708
  expect(stdout).to include("smartest/**/*_test.rb")
1665
1709
  end
1666
1710
 
1711
+ test("cli explains how to initialize when default smartest directory is missing") do
1712
+ Dir.mktmpdir do |dir|
1713
+ stdout, stderr, status = Open3.capture3(
1714
+ { "RUBYLIB" => File.expand_path("../lib", __dir__) },
1715
+ "ruby",
1716
+ File.expand_path("../exe/smartest", __dir__),
1717
+ chdir: dir
1718
+ )
1719
+
1720
+ expect(status.success?).to eq(false)
1721
+ expect(stderr).to eq("")
1722
+ expect(stdout).to include("No smartest/ directory found.")
1723
+ expect(stdout).to include("bundle exec smartest --init")
1724
+ expect(stdout).to include("bundle exec smartest --init-browser")
1725
+ expect(stdout).to include("bundle exec smartest --help")
1726
+ end
1727
+ end
1728
+
1729
+ test("cli still runs explicit paths when default smartest directory is missing") do
1730
+ Dir.mktmpdir do |dir|
1731
+ File.write(File.join(dir, "sample_test.rb"), <<~RUBY)
1732
+ test("explicit path") do
1733
+ expect(1).to eq(1)
1734
+ end
1735
+ RUBY
1736
+
1737
+ stdout, stderr, status = Open3.capture3(
1738
+ { "RUBYLIB" => File.expand_path("../lib", __dir__) },
1739
+ "ruby",
1740
+ File.expand_path("../exe/smartest", __dir__),
1741
+ "sample_test.rb",
1742
+ chdir: dir
1743
+ )
1744
+
1745
+ expect(status.success?).to eq(true)
1746
+ expect(stderr).to eq("")
1747
+ expect(stdout).to include("explicit path")
1748
+ expect(stdout).to include("1 test, 1 passed, 0 failed")
1749
+ end
1750
+ end
1751
+
1667
1752
  test("--profile prints the slowest tests with default count of 5") do
1668
1753
  output = StringIO.new
1669
1754
  reporter = Smartest::Reporter.new(output, profile_count: 5)
@@ -1760,7 +1845,9 @@ test("CLIArguments defaults profile count and parses --profile N") do
1760
1845
 
1761
1846
  expect(arguments.profile_count).to eq(5)
1762
1847
  expect(arguments.files).to eq(Dir["smartest/**/*_test.rb"])
1848
+ expect(arguments.default_paths?).to eq(true)
1763
1849
  expect(Smartest::CLIArguments.new(["--profile", "3"]).profile_count).to eq(3)
1850
+ expect(Smartest::CLIArguments.new(["smartest/foo_test.rb"]).default_paths?).to eq(false)
1764
1851
  end
1765
1852
 
1766
1853
  test("CLIArguments leaves unsupported profile forms as paths") do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3.alpha3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Iwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-04 00:00:00.000000000 Z
11
+ date: 2026-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -44,6 +44,7 @@ files:
44
44
  - lib/smartest.rb
45
45
  - lib/smartest/autorun.rb
46
46
  - lib/smartest/cli_arguments.rb
47
+ - lib/smartest/constant_stub_helpers.rb
47
48
  - lib/smartest/dsl.rb
48
49
  - lib/smartest/errors.rb
49
50
  - lib/smartest/execution_context.rb
@@ -96,9 +97,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
97
  version: '2.7'
97
98
  required_rubygems_version: !ruby/object:Gem::Requirement
98
99
  requirements:
99
- - - ">"
100
+ - - ">="
100
101
  - !ruby/object:Gem::Version
101
- version: 1.3.1
102
+ version: '0'
102
103
  requirements: []
103
104
  rubygems_version: 3.4.19
104
105
  signing_key: