minitest-markdown 0.0.1 → 0.1.0.pre
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/CHANGELOG.md +0 -28
- data/README.md +35 -96
- data/lib/minitest/markdown/configuration.rb +10 -13
- data/lib/minitest/markdown/{errors.rb → error.rb} +3 -1
- data/lib/minitest/markdown/stubb.rb +16 -0
- data/lib/minitest/markdown/test_class.rb +38 -55
- data/lib/minitest/markdown/{ruby_code_block.rb → test_code_block.rb} +15 -25
- data/lib/minitest/markdown/version.rb +1 -1
- data/lib/minitest/markdown.rb +2 -8
- metadata +5 -5
- data/lib/minitest/stub_chain.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4e1e286d2eead001d4b6e0d361aa9bb5c0a7019a5eb8d72eacf6635003e59da
|
4
|
+
data.tar.gz: 3f95349b886aa8752280e2fb2b5083d5bfc754ec8c4faed065dbb2a6e9b4d7e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd12cc6d7df1724100119b6e03e5960411d7d83b4de26beb0d4bbde0cc3e3f7a66e94901e5f99f051a82f70e058ffa1b103ee0726d103765bb9c405d661387e1
|
7
|
+
data.tar.gz: 11a4a61ba9044aec6811b9df724a111fa5345b29d837d91c8ee76a73e95939ca760243423e40127597d3b3ea0f040e633e31001d4268f33c8e06b5545efa0e06
|
data/CHANGELOG.md
CHANGED
@@ -1,33 +1,5 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
-
## [0.0.1] - 2025-05-22
|
4
|
-
|
5
|
-
- Fix bug with Minitest::Hooks method definitions
|
6
|
-
|
7
|
-
## [0.0.0] - 2025-05-10
|
8
|
-
|
9
|
-
- Fix bug in TestCodeBlock error message
|
10
|
-
- Add binding to Kernel.eval fixes #1
|
11
|
-
- pass kwargs to stub methods in StubProc
|
12
|
-
|
13
|
-
## [0.2.2.pre] - 2025-05-08
|
14
|
-
|
15
|
-
- Improve magic comment & hook comment parsing
|
16
|
-
- Fix bug with empty sting after magic comment
|
17
|
-
- Fix bug in config if Bundler undefined
|
18
|
-
|
19
|
-
## [0.2.1.pre] - 2025-05-07
|
20
|
-
|
21
|
-
- Fix issue with require in eval
|
22
|
-
- Fix bug in StubChain where instance was mutated after #call
|
23
|
-
|
24
|
-
## [0.2.0.pre] - 2025-05-06
|
25
|
-
|
26
|
-
- Add ability to pass any number of stubs to a test code block
|
27
|
-
|
28
|
-
### Breaking changes
|
29
|
-
- StubChain class replaces Stubb module see https://gitlab.com/matzfan/minitest-markdown#stubbing
|
30
|
-
|
31
3
|
## [0.1.0.pre] - 2025-05-01
|
32
4
|
|
33
5
|
- Add ability to pass stubs to test class
|
data/README.md
CHANGED
@@ -5,11 +5,10 @@
|
|
5
5
|
|
6
6
|
## \_why?
|
7
7
|
|
8
|
-
Document your Gem's usage, examples etc. with fully testable code! Better still, use your README as a BDD aid and specify
|
8
|
+
Document your Gem's usage, examples etc. with fully testable code! Better still, use your README as a BDD aid and specify functionaility in your README/wiki code blocks *before* you write your code!!
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
12
|
-
|
13
12
|
Add the gem to the application's Gemfile:
|
14
13
|
|
15
14
|
```bash
|
@@ -22,17 +21,6 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
22
21
|
gem install minitest-markdown
|
23
22
|
```
|
24
23
|
|
25
|
-
## Configuration
|
26
|
-
|
27
|
-
No configuration is required if you use Bundler. If not, set your `project_root` path using the setter method:
|
28
|
-
```ruby
|
29
|
-
@config = Minitest::Markdown.config
|
30
|
-
# => instance_of Configuration
|
31
|
-
|
32
|
-
Configuration.instance_methods(false)
|
33
|
-
# => includes :project_root=
|
34
|
-
```
|
35
|
-
|
36
24
|
## Usage
|
37
25
|
|
38
26
|
### In your test class
|
@@ -41,24 +29,17 @@ To test the Ruby blocks in your README file, create file `test_readme.rb` (for e
|
|
41
29
|
```ruby
|
42
30
|
require 'minitest/autorun' # or in your test_helper
|
43
31
|
require 'minitest/markdown' # ditto
|
44
|
-
require 'minitest/hooks/test' # optional, see 'State' section below
|
45
32
|
|
46
33
|
class ReadmeTest < Minitest::Test # or your own subclass of Minitest::Test
|
47
|
-
include Minitest::Hooks # optional, see 'State' section below
|
48
|
-
|
49
34
|
Markdown.generate_markdown_tests(self)
|
50
35
|
end
|
51
|
-
# =>
|
36
|
+
# => nil
|
52
37
|
```
|
53
38
|
To test Ruby blocks in another Markdown file, create another test file and pass the path to your Markdown file using the `:path` keyword arg i.e. `Markdown.generate_markdown_tests(self, path: '/path/to/your/markdown/file.md')`
|
54
39
|
|
55
40
|
### In your Markdown - magic comments become assertions
|
56
41
|
|
57
|
-
Each Markdown file is represented by a single test class and each Ruby block
|
58
|
-
|
59
|
-
\*Any 'state' blocks are excluded from indexing - see State section below
|
60
|
-
|
61
|
-
The syntax used for assertions is `# => ` followed by an assertion keyword. Keywords may be any one of the [Minitest "assert_" assertions](https://docs.seattlerb.org/minitest/Minitest/Assertions.html) less the "assert_" prefix (refutations are not implemented at this time). If the keyword is omitted, the default assertion; `assert_equal` is used. The actual value passed to the assertion is the result of the evaluation of the Ruby code above each magic comment. The following block (a single test) includes 6 assertions:
|
42
|
+
Each Markdown file is represented by a single test class and each Ruby block in a file becomes a test method with zero or more assertions. The syntax used is `# => ` followed by an assertion keyword. Keywords may be one of the [Minitest "assert_" assertions](https://docs.seattlerb.org/minitest/Minitest/Assertions.html) less the "assert_" prefix (refutations are not implemented at this time). If the keyword is omitted, the default assertion; `assert_equal` is used. The actual value passed to the assertion is the result of the evaluation of the Ruby code above each magic comment. The following block (a single test) includes 3 assertions:
|
62
43
|
```ruby
|
63
44
|
File.read 'test/fixtures/klass.rb'
|
64
45
|
# => "class Klass\n def hello\n 'Hello Markdown!'\n end\nend"
|
@@ -72,15 +53,12 @@ Klass.new # inline comments are also ignored
|
|
72
53
|
# The assertion and expected value are:-
|
73
54
|
# => instance_of Klass
|
74
55
|
|
75
|
-
# No
|
56
|
+
# No keywword here, so the default assertion is used (assert_equal)
|
76
57
|
Klass.new.hello
|
77
58
|
# => 'Hello Markdown!'
|
78
59
|
|
79
60
|
Klass.hello
|
80
61
|
# => raises NoMethodError
|
81
|
-
|
82
|
-
self
|
83
|
-
# => instance_of Markdown::TestClass
|
84
62
|
```
|
85
63
|
Plain old `assert` has been aliased as `assert_truthy`, so when expecting a truthy value you should do this:
|
86
64
|
```ruby
|
@@ -94,7 +72,7 @@ For convenience, the assertion `assert_includes` has also been aliased so that i
|
|
94
72
|
2
|
95
73
|
# => included_in [1, 2, 3]
|
96
74
|
```
|
97
|
-
Everything on the magic comment line after the
|
75
|
+
Everything on the magic comment line after the keyword, or `# => `, if one is omitted, is evaluated as Ruby code. Note: **inline comments are NOT ALLOWED on magic comment lines**. Where an assertion takes multiple positional args, these are simply separated by commas. Note that the assertion keyword itself is **not an argument**. The syntax is as follows:
|
98
76
|
```ruby
|
99
77
|
22/7.0
|
100
78
|
# => in_delta Math::PI, 0.01
|
@@ -104,102 +82,63 @@ To skip a test, use `skip`, as you would in a regular test:
|
|
104
82
|
"some code which you don't want to test yet"
|
105
83
|
# => skip 'give a reason for the skip here'
|
106
84
|
```
|
107
|
-
Test failures will look like this - note the method name `test_block10` in this example:
|
108
|
-
```
|
109
|
-
Minitest::Markdown::ReadmeTest#test_block10 [lib/minitest/markdown/test_class.rb:118]:
|
110
|
-
Expected: 42
|
111
|
-
Actual: 0
|
112
|
-
```
|
113
85
|
|
114
86
|
### State
|
115
87
|
|
116
|
-
|
117
|
-
```ruby
|
118
|
-
@instance_var
|
119
|
-
# => 7
|
120
|
-
|
121
|
-
@before_all_instance_var # see hook methods below
|
122
|
-
# => 'foo'
|
123
|
-
```
|
124
|
-
Minitest's `setup` and `teardown` methods are generated by using the appropriate comment on the first line of a code block. Assertion magic comments are ignored in such blocks, as these are not tests. E.g.
|
125
|
-
|
88
|
+
Minitest's `setup` and `teardown` methods are generated by using the appropriate comment on the first line of a code block. Magic comments are ignored in such blocks, as these are not tests. E.g.
|
126
89
|
```ruby
|
127
90
|
# setup
|
128
91
|
|
129
|
-
|
130
|
-
@instance_var = 7 # now available in all test method blocks, including the one above
|
131
|
-
# => IGNORED
|
92
|
+
@instance_var = 42
|
132
93
|
```
|
94
|
+
The instance var set is now available to all test blocks (tests) in the Markdown file.
|
133
95
|
```ruby
|
134
|
-
|
135
|
-
|
136
|
-
# do some teardown task
|
96
|
+
@instance_var
|
97
|
+
# => 42
|
137
98
|
```
|
138
|
-
The hook methods defined in the [minitest-hooks](https://github.com/jeremyevans/minitest-hooks?tab=readme-ov-file#in-tests-minitesttest-) extension (`before_all`, `after_all`, `around` & `around_all`)are also available in this way if `minitest-hooks` is installed and `Minitest::Hooks` is included in your
|
99
|
+
The hook methods defined in the [minitest-hooks](https://github.com/jeremyevans/minitest-hooks?tab=readme-ov-file#in-tests-minitesttest-) extension (`before_all`, `after_all`, `around` & `around_all`)are also available in this way if `minitest-hooks` is installed and `Minitest::Hooks` is included in your test class.
|
139
100
|
|
140
|
-
```ruby
|
141
|
-
# before_all
|
142
|
-
@before_all_instance_var = 'foo'
|
143
|
-
```
|
144
101
|
|
145
|
-
Everything in the
|
102
|
+
Everything in the code blocks above runs as test code. [minitest-proveit](https://github.com/seattlerb/minitest-proveit) would complain otherwise ;-)
|
146
103
|
|
147
|
-
|
104
|
+
## Stubbing
|
148
105
|
|
149
|
-
|
150
|
-
```ruby
|
151
|
-
@mymock = Minitest::Mock.new
|
152
|
-
@mymock.expect(:puts, nil, ['Hello World!'])
|
153
|
-
|
154
|
-
@mymock.puts 'Hello World!'
|
155
|
-
# => mock @mymock
|
156
|
-
```
|
157
|
-
|
158
|
-
### Stubbing
|
159
|
-
|
160
|
-
It is possible to pass stubs to the generated tests. This is done using the stubs keyword. Hash keys represent the index of the test code block. **Important: any 'state' blocks are ignored for test code block indexing**. The hash value must be an instance of `Minitest::StubChain` which holds a proc for each stub in the chain. For convenience `StubChain.stubproc` returns a suitable stub proc object which is called around the relevant test code. Zero or more of these reusable procs can be used to instantiate a StubChain object:
|
106
|
+
It is possible to pass stubs to the generated tests. This is done by passing a hash of procs using the stubs keyword. Hash keys represent the index of the text code block and the key is a proc which is called around the relevant test code. 2 convenience methods are provided by the `Markdown::Stubb` module. In your markdown test:
|
161
107
|
```ruby
|
162
108
|
class MarkdownTest < Minitest::Test
|
163
|
-
|
164
|
-
array_stubs_size = Minitest::StubChain.stubproc(Array, :size, 42, any_instance: true) # uses the bundled minitest-stub_any_instance gem
|
165
|
-
|
166
|
-
stub_chain = Minitest::StubChain.new([set_stubs_new]) # initialized with zero or more stub procs
|
167
|
-
stub_chain.stubs << array_stubs_size # add another like this if need be
|
109
|
+
extend Markdown::Stubb # for the stubb & stubb_any_instance methods
|
168
110
|
|
169
111
|
stubs = {}
|
170
|
-
stubs[
|
171
|
-
stubs[
|
112
|
+
stubs[8] = stubb(Set, :new, []) # returns a proc
|
113
|
+
stubs[9] = stubb_any_instance(Set, :size, 42) # uses the bundled `minitest-stub_any_instance` extension
|
172
114
|
|
173
115
|
Markdown.generate_markdown_tests(self, stubs: stubs)
|
174
116
|
end
|
175
|
-
# =>
|
117
|
+
# => nil
|
176
118
|
```
|
177
|
-
The 2 stubs
|
119
|
+
The 2 stubs above are demonstated in the following examples:
|
178
120
|
```ruby
|
179
|
-
# This is
|
180
|
-
Set.new([1, 'c', :s]).
|
181
|
-
# =>
|
182
|
-
|
183
|
-
# Because we added `stubs[10] = stub_chain` above, this is exactly equivalent to:
|
184
|
-
#
|
185
|
-
# def test_block10
|
186
|
-
# Set.stub(:new, []) do
|
187
|
-
# Array.stub_any_instance(:size, 42) do
|
188
|
-
# assert_equal 42, Set.new([1, 'c', :s]).size
|
189
|
-
# end
|
190
|
-
# end
|
191
|
-
# end
|
121
|
+
# This is test_block8
|
122
|
+
Set.new([1, 'c', :s]) # Set.new stubbed to return an Array
|
123
|
+
# => instance_of Array
|
192
124
|
```
|
193
|
-
Example showing the reuse of a StubChain:
|
194
125
|
```ruby
|
195
|
-
# This is
|
196
|
-
Set.new.size
|
126
|
+
# This is test_block9
|
127
|
+
Set.new([1, 'c', :s]).size # Set#size stubbed to return 42
|
197
128
|
# => 42
|
198
129
|
```
|
130
|
+
Presently only one stub per test is possible.
|
131
|
+
|
132
|
+
## Configuration
|
199
133
|
|
200
|
-
|
134
|
+
No configuation is required if you use Bundler. If not, set your `project_root` path using the setter method:
|
135
|
+
```ruby
|
136
|
+
@config = Markdown.config
|
137
|
+
# => instance_of Configuration
|
201
138
|
|
202
|
-
|
139
|
+
Configuration.instance_methods(false)
|
140
|
+
# => includes :project_root=
|
141
|
+
```
|
203
142
|
|
204
143
|
## Development
|
205
144
|
|
@@ -209,7 +148,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
209
148
|
|
210
149
|
## Contributing
|
211
150
|
|
212
|
-
Bug reports and pull requests are welcome on
|
151
|
+
Bug reports and pull requests are welcome on GitHub at https://gitlab.com/matzfan/minitest-markdown. Please checkout a suitably named branch before submitting a PR.
|
213
152
|
|
214
153
|
## License
|
215
154
|
|
@@ -2,32 +2,29 @@
|
|
2
2
|
|
3
3
|
require 'pathname'
|
4
4
|
|
5
|
-
require_relative '
|
5
|
+
require_relative 'error'
|
6
6
|
|
7
7
|
module Minitest
|
8
8
|
module Markdown
|
9
9
|
# configuration for gem
|
10
10
|
class Configuration
|
11
|
-
|
12
|
-
|
13
|
-
attr_writer :project_root
|
14
|
-
attr_accessor :prove_it
|
11
|
+
attr_accessor :project_root, :prove_it
|
15
12
|
|
16
13
|
def initialize
|
17
|
-
@project_root =
|
14
|
+
@project_root = determine_project_root
|
18
15
|
@prove_it = true
|
19
16
|
end
|
20
17
|
|
21
|
-
def
|
22
|
-
|
18
|
+
def readme_path
|
19
|
+
project_root.join 'README.md'
|
23
20
|
end
|
24
21
|
|
25
|
-
|
26
|
-
@project_root || (raise Error, PROJECT_ROOT_UNDETERMINED)
|
27
|
-
end
|
22
|
+
private
|
28
23
|
|
29
|
-
def
|
30
|
-
|
24
|
+
def determine_project_root
|
25
|
+
return Bundler.root if defined? Bundler
|
26
|
+
|
27
|
+
raise Error::MarkdownError, "Project root can't be determined, set with 'Config.project_root = /root/path'"
|
31
28
|
end
|
32
29
|
end
|
33
30
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module Markdown
|
5
|
+
# a proc'ed stub
|
6
|
+
module Stubb
|
7
|
+
def stubb(object, method, ret_val, *args, **kwargs)
|
8
|
+
proc { |&blk| object.stub(method, ret_val, *args, **kwargs) { blk.call } }
|
9
|
+
end
|
10
|
+
|
11
|
+
def stubb_any_instance(object, method, ret_val, *args, **kwargs)
|
12
|
+
proc { |&blk| object.stub_any_instance(method, ret_val, *args, **kwargs) { blk.call } }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,88 +1,78 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../assertions_extensions'
|
4
|
-
require_relative '
|
5
|
-
|
6
|
-
$LOAD_PATH << '.' # fix #2
|
4
|
+
require_relative 'test_code_block'
|
7
5
|
|
8
6
|
module Minitest
|
9
7
|
module Markdown
|
10
|
-
# knows how to build a markdown test
|
8
|
+
# knows how to build a markdown test
|
11
9
|
class TestClass
|
12
|
-
FENCED_BLOCK_REGEXP = /^```ruby\n(.*?)\n```/m
|
13
|
-
|
14
|
-
|
15
|
-
BAD_PATH = 'Path does not exist, is not readable or is not a Markdown file:'
|
16
|
-
MD = '.md'
|
17
|
-
|
18
|
-
HOOK_METHODS_BEFORE = %i[before_all].freeze
|
19
|
-
HOOK_METHODS_AFTER = %i[after_all around around_all].freeze
|
10
|
+
FENCED_BLOCK_REGEXP = /^```ruby\n(.*?)\n```/m # TODO: use Lexer/RDoc::Parser? which can deal with md comments
|
11
|
+
HOOK_METHODS = %i[before_all after_all around around_all].freeze # minitest-hooks extension
|
12
|
+
NON_TEST_METHODS = %i[setup teardown].freeze
|
20
13
|
|
21
14
|
attr_reader :klass, :path
|
22
15
|
|
23
16
|
def initialize(klass, path: nil)
|
24
|
-
@klass =
|
25
|
-
@
|
26
|
-
@
|
17
|
+
@klass = klass
|
18
|
+
@config = Markdown.config
|
19
|
+
@path = path || @config.readme_path
|
20
|
+
@ruby_blocks = parse_ruby_blocks
|
27
21
|
end
|
28
22
|
|
29
23
|
def define_methods(stubs: {})
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
define_test_method(block, i, stub_chain: stubs[i])
|
35
|
-
i += 1
|
36
|
-
end
|
24
|
+
define_non_test_methods
|
25
|
+
@ruby_blocks.each_with_index { |block, i| define_test_method(block, i, stubs[i]) }
|
26
|
+
nil
|
37
27
|
end
|
38
28
|
|
39
29
|
private
|
40
30
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
klass
|
31
|
+
def parse_ruby_blocks
|
32
|
+
File.new(path).read.scan(FENCED_BLOCK_REGEXP).flatten.map { |str| TestCodeBlock.new(str) }
|
45
33
|
end
|
46
34
|
|
47
|
-
def
|
48
|
-
|
49
|
-
raise ArgumentError, "#{BAD_PATH} #{pth}" unless File.exist?(pth) && File.file?(pth) && File.extname(pth) == MD
|
50
|
-
|
51
|
-
pth
|
35
|
+
def define_non_test_methods
|
36
|
+
recognized_non_test_methods.each { |name| define_non_test_method(name, parse_non_test_method_block(name)) }
|
52
37
|
end
|
53
38
|
|
54
|
-
def
|
55
|
-
|
39
|
+
def recognized_non_test_methods
|
40
|
+
Minitest.const_defined?(:Hooks) ? NON_TEST_METHODS + HOOK_METHODS : NON_TEST_METHODS
|
56
41
|
end
|
57
42
|
|
58
|
-
def define_non_test_method(block)
|
59
|
-
|
60
|
-
raise Error, "State method: #{type} is already defined" if @state_methods_defined.include? type
|
43
|
+
def define_non_test_method(type, block)
|
44
|
+
return unless block
|
61
45
|
|
62
46
|
bind = binding
|
63
47
|
klass.define_method(type) do
|
64
|
-
super() if
|
65
|
-
bind.eval(block
|
66
|
-
super() if
|
48
|
+
super() if type == :before_all
|
49
|
+
bind.eval(block)
|
50
|
+
super() if %i[after_all around around_all].include? type
|
67
51
|
end
|
68
|
-
@state_methods_defined << type
|
69
52
|
end
|
70
53
|
|
71
|
-
def
|
72
|
-
|
54
|
+
def parse_non_test_method_block(type)
|
55
|
+
blocks = @ruby_blocks.map.select { |blk| blk.fenced_block_str.lines.first == "# #{type}\n" }
|
56
|
+
return if blocks.empty?
|
57
|
+
raise Error::MarkdownError, "Multiple #{type} blocks are not allowed" if blocks.size > 1
|
58
|
+
|
59
|
+
@ruby_blocks.delete(blocks.first).fenced_block_str # hook method blocks can't be test methods
|
60
|
+
end
|
61
|
+
|
62
|
+
def define_test_method(block, meth_index, stub = nil)
|
63
|
+
stub ||= proc { |&blk| blk.call } # empty proc if no stub provided
|
73
64
|
instance = self # scope
|
74
65
|
klass.define_method(:"test_block#{meth_index}") do
|
75
|
-
|
66
|
+
stub.call do
|
76
67
|
block.assertions.each { |assertion_hash| instance.send(:evaluation_assertions, assertion_hash, binding) }
|
77
68
|
end
|
78
|
-
stub_chain.call(test_code_proc)
|
79
69
|
end
|
80
70
|
end
|
81
71
|
|
82
72
|
def evaluation_assertions(assertion_hash, bind)
|
83
|
-
ruby, assertion, args =
|
73
|
+
ruby, assertion, args = TestCodeBlock::ASSERTION_KEYS.map { |key| assertion_hash[key] } # order dep
|
84
74
|
|
85
|
-
lmbda = -> {
|
75
|
+
lmbda = -> { eval(ruby) } # rubocop:disable Security/Eval
|
86
76
|
return unless assertion
|
87
77
|
return eval_with_block(bind, assertion, lmbda, args) if Assertions::WITH_BLOCK_EVAL.include? assertion
|
88
78
|
|
@@ -92,27 +82,20 @@ module Minitest
|
|
92
82
|
def eval_with_block(bind, assertion, actual, args)
|
93
83
|
return bind.receiver.send(assertion, &actual) if args.empty?
|
94
84
|
|
95
|
-
bind.receiver.send(assertion, *
|
85
|
+
bind.receiver.send(assertion, *eval(args), &actual) # rubocop:disable Security/Eval
|
96
86
|
end
|
97
87
|
|
98
88
|
def eval_without_block(bind, assertion, lmbda, args)
|
99
89
|
actual = lmbda.call
|
100
90
|
return bind.receiver.send(assertion, actual) if args == '[]'
|
101
91
|
|
102
|
-
expected, *rest =
|
92
|
+
expected, *rest = eval(args) # rubocop:disable Security/Eval
|
103
93
|
expected, actual = actual, expected if Assertions::EXPECTED_ACTUAL_REVERSED.include? assertion
|
104
94
|
kwargs = rest.last.is_a?(Hash) ? rest.last : nil
|
105
95
|
return bind.receiver.send(assertion, expected, actual, *rest) unless kwargs
|
106
96
|
|
107
97
|
bind.receiver.send(assertion, expected, actual, *rest, **kwargs) # assert_respond_to takes a kwarg..
|
108
98
|
end
|
109
|
-
|
110
|
-
def kernel_eval(str)
|
111
|
-
bind = binding
|
112
|
-
bind.eval str
|
113
|
-
rescue SyntaxError
|
114
|
-
raise ArgumentError, "Invalid test code, failed to parse #{str}"
|
115
|
-
end
|
116
99
|
end
|
117
100
|
end
|
118
101
|
end
|
@@ -1,47 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'minifyrb'
|
3
|
+
require 'minifyrb' # DEP
|
4
4
|
|
5
5
|
require_relative '../assertions_extensions' # for assert_truthy
|
6
|
-
require_relative '../markdown/
|
6
|
+
require_relative '../markdown/error'
|
7
7
|
|
8
8
|
module Minitest
|
9
9
|
module Markdown
|
10
|
-
# A Ruby code block
|
11
|
-
class
|
12
|
-
ASSERT_
|
13
|
-
ASSERT_REGEXP
|
14
|
-
SKIP
|
10
|
+
# A Ruby code block reprenting test code, with 0 or more assertions
|
11
|
+
class TestCodeBlock
|
12
|
+
ASSERT_ = 'assert_'
|
13
|
+
ASSERT_REGEXP = /\A#{ASSERT_}/
|
14
|
+
SKIP = { skip: :skip }.freeze
|
15
15
|
DEFAULT_ASSERTION = :assert_equal
|
16
16
|
|
17
|
-
ASSERTION_KEYS
|
18
|
-
STATE_BLOCK_TYPES = %i[setup teardown before_all after_all around around_all].freeze
|
17
|
+
ASSERTION_KEYS = %i[ruby assertion test_args].freeze
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
MISSING_ASSERTION_OR_VALUE = "Magic comment missing assertion or value. Something must follow '# =>'"
|
19
|
+
MAGIC_COMMENT_DELIMITER = '# => '
|
20
|
+
MAGIC_COMMENT_REGEXP = /^#{MAGIC_COMMENT_DELIMITER}.*$/
|
21
|
+
MAGIC_COMMENT_SCAN_REGEXP = /^#{MAGIC_COMMENT_DELIMITER}(.*)$/ # strips delimiter prefix
|
24
22
|
|
25
23
|
attr_reader :fenced_block_str
|
26
24
|
|
27
25
|
def self.asserts
|
28
|
-
|
26
|
+
Assertions.instance_methods.grep(ASSERT_REGEXP) - [:assert_send] # deprecated
|
29
27
|
end
|
30
28
|
|
31
29
|
def self.assertions_map
|
32
|
-
|
30
|
+
asserts.inject({}) { |memo, m| memo.merge(m.to_s.split(ASSERT_)[1..].join.to_sym => m) }.merge(SKIP)
|
33
31
|
end
|
34
32
|
|
35
33
|
def initialize(fenced_block_str)
|
36
34
|
@fenced_block_str = fenced_block_str
|
37
35
|
end
|
38
36
|
|
39
|
-
def type
|
40
|
-
STATE_BLOCK_TYPES.each { |type| return type if fenced_block_str.lines.first&.match?(/^\s*#\s*#{type}\s*\n$/) }
|
41
|
-
|
42
|
-
:test
|
43
|
-
end
|
44
|
-
|
45
37
|
def assertions
|
46
38
|
assertions_arr.map { |arr| Hash[*ASSERTION_KEYS.zip(arr).flatten] }
|
47
39
|
end
|
@@ -53,8 +45,6 @@ module Minitest
|
|
53
45
|
end
|
54
46
|
|
55
47
|
def parse(magic_comment_string)
|
56
|
-
raise ArgumentError, MISSING_ASSERTION_OR_VALUE if magic_comment_string.strip.empty?
|
57
|
-
|
58
48
|
assertion = parse_assertion(magic_comment_string)
|
59
49
|
return [DEFAULT_ASSERTION, "[#{magic_comment_string}]"] if assertion.nil?
|
60
50
|
return [assertion, '[]'] if magic_comment_string.split.size == 1
|
@@ -63,7 +53,7 @@ module Minitest
|
|
63
53
|
end
|
64
54
|
|
65
55
|
def parse_assertion(str)
|
66
|
-
self.class.assertions_map[str.split.first.to_sym]
|
56
|
+
self.class.assertions_map[str.split.first.to_sym]
|
67
57
|
end
|
68
58
|
|
69
59
|
def magic_comments
|
@@ -74,7 +64,7 @@ module Minitest
|
|
74
64
|
@fenced_block_str.split(MAGIC_COMMENT_REGEXP).map do |str|
|
75
65
|
Minifyrb::Minifier.new(str).minify.strip
|
76
66
|
rescue SyntaxError
|
77
|
-
raise Error, "Syntax error in:\n\n#{str}"
|
67
|
+
raise Error::MarkdownError, "Syntax error in:\n\n#{str}"
|
78
68
|
end
|
79
69
|
end
|
80
70
|
end
|
data/lib/minitest/markdown.rb
CHANGED
@@ -2,17 +2,15 @@
|
|
2
2
|
|
3
3
|
require 'minitest/stub_any_instance'
|
4
4
|
|
5
|
-
require_relative 'stub_chain'
|
6
5
|
require_relative 'markdown/configuration'
|
7
|
-
require_relative 'markdown/
|
6
|
+
require_relative 'markdown/error'
|
7
|
+
require_relative 'markdown/stubb'
|
8
8
|
require_relative 'markdown/version'
|
9
9
|
require_relative 'markdown/test_class'
|
10
10
|
|
11
11
|
module Minitest
|
12
12
|
# namespace
|
13
13
|
module Markdown
|
14
|
-
ARG_ERR = 'stubs keyword takes a hash. Keys are integers and values are StubChain instances'
|
15
|
-
|
16
14
|
class << self
|
17
15
|
def config
|
18
16
|
@config ||= Configuration.new
|
@@ -23,10 +21,6 @@ module Minitest
|
|
23
21
|
end
|
24
22
|
|
25
23
|
def generate_markdown_tests(klass, path: nil, stubs: {})
|
26
|
-
raise ArgumentError, ARG_ERR unless stubs.is_a? Hash
|
27
|
-
raise ArgumentError, ARG_ERR unless stubs.keys.all? { |o| o.instance_of? Integer }
|
28
|
-
raise ArgumentError, ARG_ERR unless stubs.values.all? { |o| o.instance_of? StubChain }
|
29
|
-
|
30
24
|
TestClass.new(klass, path: path).define_methods(stubs: stubs)
|
31
25
|
end
|
32
26
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-markdown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.1.0.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- MatzFan
|
@@ -77,11 +77,11 @@ files:
|
|
77
77
|
- lib/minitest/assertions_extensions.rb
|
78
78
|
- lib/minitest/markdown.rb
|
79
79
|
- lib/minitest/markdown/configuration.rb
|
80
|
-
- lib/minitest/markdown/
|
81
|
-
- lib/minitest/markdown/
|
80
|
+
- lib/minitest/markdown/error.rb
|
81
|
+
- lib/minitest/markdown/stubb.rb
|
82
82
|
- lib/minitest/markdown/test_class.rb
|
83
|
+
- lib/minitest/markdown/test_code_block.rb
|
83
84
|
- lib/minitest/markdown/version.rb
|
84
|
-
- lib/minitest/stub_chain.rb
|
85
85
|
- sig/minitest/markdown.rbs
|
86
86
|
homepage: https://gitlab.com/matzfan/minitest-markdown
|
87
87
|
licenses:
|
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
104
|
- !ruby/object:Gem::Version
|
105
105
|
version: '0'
|
106
106
|
requirements: []
|
107
|
-
rubygems_version: 3.
|
107
|
+
rubygems_version: 3.6.8
|
108
108
|
specification_version: 4
|
109
109
|
summary: Turn your README.md Ruby code blocks into testable code.
|
110
110
|
test_files: []
|
data/lib/minitest/stub_chain.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Minitest
|
4
|
-
# respresentaion of zero or more stub blocks around one or more test assertions
|
5
|
-
class StubChain
|
6
|
-
NOT_CALLABLE_ERR = 'StubChain#call takes a callable argument'
|
7
|
-
MUST_NOT_CALL_A_BLOCK_ERR = 'StubChain#call takes a callable argument which must not call a block'
|
8
|
-
|
9
|
-
attr_reader :stubs
|
10
|
-
|
11
|
-
def initialize(stubs = [])
|
12
|
-
@stubs = *stubs
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.stubproc(klass, *args, any_instance: false, **kwargs)
|
16
|
-
return proc { |&blk| klass.stub(*args, **kwargs) { blk.call } } unless any_instance
|
17
|
-
|
18
|
-
proc { |&blk| klass.stub_any_instance(*args, **kwargs) { blk.call } }
|
19
|
-
end
|
20
|
-
|
21
|
-
def call(test_code_proc)
|
22
|
-
validate(test_code_proc)
|
23
|
-
call_recursive [*stubs, test_code_proc]
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def validate(test_code_proc)
|
29
|
-
raise ArgumentError, NOT_CALLABLE_ERR unless test_code_proc.respond_to?(:call)
|
30
|
-
|
31
|
-
test_code_proc
|
32
|
-
end
|
33
|
-
|
34
|
-
def call_recursive(call_chain)
|
35
|
-
prok = call_chain.shift
|
36
|
-
return prok.call if call_chain.empty?
|
37
|
-
|
38
|
-
prok.call { call_recursive(call_chain) }
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|