minitestify 0.1.0 → 0.2.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: 5b8d0d4202c4c1c1aba785fcea7db1810ee21e58a1f6f904b0d95f2f8199490a
4
- data.tar.gz: 0a9a0126d2f41ec3b688fd87562850a301bd12515eed86c0589530f41feded07
3
+ metadata.gz: b169c65b41f33533e17a58e0cd66140a64d08039f6e20df0679d5746ac4ff99b
4
+ data.tar.gz: 6207f274d48bf806f3dcf2f490ffe1cc44d4aa0a4a36b521b47b8caaa8af01f8
5
5
  SHA512:
6
- metadata.gz: '080b030988c144384db8b694691acf0ece4dfbf013897753850fd8a1eea006016653b0726577e302f6b3796ca968c704c8a293258462b4a39a55d27060cd0d22'
7
- data.tar.gz: 040341ca4fb0a102b8545d66bc5254054e16341c7c71e77933b50c957a916b12fca8fb0fec49ec002ade98bcbe5586ef05736b623ce8c7d2d095345d0d5bf052
6
+ metadata.gz: 0af88789ce5f56b3170cbb5fdb4fad7a68f890e09bc1f6f2748466e5c25734d5a0b8d6a02b344fbc492fc229be6392677c37ffd1e77a9d41093d5cd990fe38e2
7
+ data.tar.gz: ad16675adb29636e70805c58cde16a43871588f5e1171dbe585a2cec43caf6bf9a54ae8c6ff9944630906091b5be5dce8cbd23fe23f014f146b2460ab0898ab9
data/README.md CHANGED
@@ -1,8 +1,13 @@
1
1
  # Minitestify
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/minitestify`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ minitestify is a tool to convert Rspec specs to equivalent minitest tests by parsing and transforming the code using the [syntax_tree](https://github.com/ruby-syntax-tree/syntax_tree) gem.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ It's early days and this work is still experimental. Here's what it can do now
6
+
7
+ ### Capabilities
8
+ - describe -> class
9
+ - it -> def test_*
10
+ - expect(actual).to eq(expected) -> assert_equal(expected, actual)
6
11
 
7
12
  ## Installation
8
13
 
@@ -16,7 +21,9 @@ If bundler is not being used to manage dependencies, install the gem by executin
16
21
 
17
22
  ## Usage
18
23
 
19
- TODO: Write usage instructions here
24
+ ```
25
+ minitestify print FILES # Convert one or more specs to minitest and print to standard out
26
+ ```
20
27
 
21
28
  ## Development
22
29
 
data/exe/minitestify ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "dry/cli"
6
+ require "minitestify/cli"
7
+
8
+ Dry::CLI.new(Minitestify::CLI::Commands).call
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/cli"
4
+ require "minitestify"
5
+ require "minitestify/version"
6
+ require "minitestify/spec"
7
+
8
+ module Minitestify::CLI
9
+ module Commands
10
+ extend Dry::CLI::Registry
11
+
12
+ class Version < Dry::CLI::Command
13
+ desc "Print version"
14
+
15
+ def call(*)
16
+ puts Minitestify::VERSION
17
+ end
18
+ end
19
+
20
+ class Print < Dry::CLI::Command
21
+ desc "Convert one or more specs to minitest and print to standard out"
22
+
23
+ argument :files, type: :array, required: true, desc: "Spec files to convert"
24
+
25
+ example [
26
+ "spec/dog_spec.rb # Generates dog_test.rb and prints to standard out"
27
+ ]
28
+
29
+ def call(files:, **)
30
+ files.each do |file|
31
+ spec = Minitestify::Spec.new(file: file)
32
+ puts "# #{spec.to_test_filepath}"
33
+ puts spec.to_test_code
34
+ puts
35
+ end
36
+ end
37
+ end
38
+
39
+ register "version", Version, aliases: ["v", "-v", "--version"]
40
+ register "print", Print, aliases: ["p", "-p", "--print"]
41
+ end
42
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitestify"
4
+
5
+ module Minitestify
6
+ class Spec
7
+ def initialize(file:)
8
+ @file = file
9
+ end
10
+
11
+ def to_test_filepath
12
+ @file.gsub "spec", "test"
13
+ # TODO be smarter here and dissect the file path
14
+ # replacing only the spec dir (if present)
15
+ # and the _spec.rb suffix
16
+ end
17
+
18
+ def to_test_code
19
+ source = SyntaxTree.read(@file)
20
+ program = SyntaxTree.parse(source)
21
+
22
+ visitor = SyntaxTree::Visitor::MutationVisitor.new
23
+ inflector = Dry::Inflector.new
24
+
25
+ # describe -> class
26
+ visitor.mutate("Command[ message: Ident[value: \"describe\"] ]") do |node|
27
+ node => SyntaxTree::Command[
28
+
29
+ message: SyntaxTree::Ident[value: "describe"],
30
+ arguments: SyntaxTree::Args[
31
+ parts: [SyntaxTree::VarRef[value: SyntaxTree::Const[value: value]]]
32
+ ]
33
+ ]
34
+
35
+ SyntaxTree::ClassDeclaration.new(
36
+ constant:
37
+ SyntaxTree::ConstRef.new(
38
+ constant:
39
+ SyntaxTree::Const.new(
40
+ value: "#{inflector.camelize_upper(value)}Test",
41
+ location: node.location
42
+ ),
43
+ location: node.location
44
+ ),
45
+ superclass:
46
+ SyntaxTree::ConstPathRef.new(
47
+ parent:
48
+ SyntaxTree::VarRef.new(
49
+ value:
50
+ SyntaxTree::Const.new(
51
+ value: "Minitest",
52
+ location: node.location
53
+ ),
54
+ location: node.location
55
+ ),
56
+ constant:
57
+ SyntaxTree::Const.new(value: "Test", location: node.location),
58
+ location: node.location
59
+ ),
60
+ bodystmt: node.child_nodes.last.bodystmt,
61
+ location: node.location
62
+ )
63
+ end
64
+
65
+ # it -> def
66
+ visitor.mutate("Command[message: Ident[value: \"it\"]]") do |node|
67
+ node => SyntaxTree::Command[
68
+ message: SyntaxTree::Ident[value: "it"],
69
+ arguments: SyntaxTree::Args[
70
+ parts: [
71
+ SyntaxTree::StringLiteral[
72
+ parts: [SyntaxTree::TStringContent[value: value]]
73
+ ]
74
+ ]
75
+ ]
76
+ ]
77
+
78
+ SyntaxTree::DefNode.new(
79
+ target: nil,
80
+ operator: nil,
81
+ name:
82
+ SyntaxTree::Ident.new(
83
+ value: "test_#{value.gsub(" ", "_")}",
84
+ location: node.location
85
+ ),
86
+ params: SyntaxTree::Params,
87
+ bodystmt: node.child_nodes.last.bodystmt,
88
+ location: node.location
89
+ )
90
+ end
91
+
92
+ expect_eq_search =
93
+ 'CommandCall[
94
+ receiver: CallNode[
95
+ message: Ident[value: "expect"]
96
+ ],
97
+ operator: Period[value: "."],
98
+ message: Ident[value: "to"],
99
+ arguments: Args[
100
+ parts: [ CallNode[
101
+ message: Ident[value: "eq"]
102
+ ]
103
+ ]
104
+ ]
105
+ ]'
106
+ visitor.mutate(expect_eq_search) do |node|
107
+ node => SyntaxTree::CommandCall[
108
+ receiver: SyntaxTree::CallNode[
109
+ receiver: nil,
110
+ operator: nil,
111
+ message: SyntaxTree::Ident[value: "expect"],
112
+ arguments: SyntaxTree::ArgParen[
113
+ arguments: SyntaxTree::Args[parts: [actual_expr]]
114
+ ]
115
+ ],
116
+ operator: SyntaxTree::Period[value: "."],
117
+ message: SyntaxTree::Ident[value: "to"],
118
+ arguments: SyntaxTree::Args[
119
+ parts: [
120
+ SyntaxTree::CallNode[
121
+ receiver: nil,
122
+ operator: nil,
123
+ message: SyntaxTree::Ident[value: "eq"],
124
+ arguments: SyntaxTree::ArgParen[
125
+ arguments: SyntaxTree::Args[parts: [expected_expr]]
126
+ ]
127
+ ]
128
+ ]
129
+ ]
130
+ ]
131
+
132
+ SyntaxTree::CallNode.new(
133
+ message:
134
+ SyntaxTree::Ident.new(
135
+ value: "assert_equal",
136
+ location: node.location
137
+ ),
138
+ arguments:
139
+ SyntaxTree::ArgParen.new(
140
+ arguments:
141
+ SyntaxTree::Args.new(
142
+ parts: [expected_expr, actual_expr],
143
+ location: node.location
144
+ ),
145
+ location: node.location
146
+ ),
147
+ location: node.location,
148
+ receiver: nil,
149
+ operator: nil
150
+ )
151
+ end
152
+
153
+ SyntaxTree::Formatter.format(source, program.accept(visitor))
154
+ end
155
+ end
156
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Minitestify
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/minitestify.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "minitestify/version"
4
+ require "syntax_tree"
5
+ require "dry/inflector"
4
6
 
5
7
  module Minitestify
6
- class Error < StandardError; end
7
- # Your code goes here...
8
+ class Error < StandardError
9
+ end
8
10
  end
data/minitestify.gemspec CHANGED
@@ -30,6 +30,8 @@ Gem::Specification.new do |spec|
30
30
 
31
31
  # Uncomment to register a new dependency of your gem
32
32
  spec.add_dependency "syntax_tree", "~> 5.0"
33
+ spec.add_dependency "dry-inflector", "~> 1.0"
34
+ spec.add_dependency "dry-cli", "~> 1.0"
33
35
 
34
36
  # For more information and examples about making a new gem, check out our
35
37
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -0,0 +1,237 @@
1
+ # How to add a transformation
2
+
3
+ Run the following in an irb session to determine the search pattern
4
+
5
+ ```ruby
6
+ require "syntax_tree"
7
+
8
+ code = <<~RUBY
9
+ expect(calculator.add(1, 1)).to eq(2)
10
+ RUBY
11
+
12
+ program = SyntaxTree.parse code
13
+ puts program.construct_keys
14
+ ```
15
+
16
+ You'll get a bunch of output like this
17
+ ```ruby
18
+ SyntaxTree::Program[
19
+ statements: SyntaxTree::Statements[
20
+ body: [
21
+ SyntaxTree::CommandCall[
22
+ receiver: SyntaxTree::CallNode[
23
+ receiver: nil,
24
+ operator: nil,
25
+ message: SyntaxTree::Ident[value: "expect"],
26
+ arguments: SyntaxTree::ArgParen[
27
+ arguments: SyntaxTree::Args[
28
+ parts: [
29
+ SyntaxTree::CallNode[
30
+ receiver: SyntaxTree::VCall[
31
+ value: SyntaxTree::Ident[value: "calculator"]
32
+ ],
33
+ operator: SyntaxTree::Period[value: "."],
34
+ message: SyntaxTree::Ident[value: "add"],
35
+ arguments: SyntaxTree::ArgParen[
36
+ arguments: SyntaxTree::Args[
37
+ parts: [
38
+ SyntaxTree::Int[value: "1"],
39
+ SyntaxTree::Int[value: "1"]
40
+ ]
41
+ ]
42
+ ]
43
+ ]
44
+ ]
45
+ ]
46
+ ]
47
+ ],
48
+ operator: SyntaxTree::Period[value: "."],
49
+ message: SyntaxTree::Ident[value: "to"],
50
+ arguments: SyntaxTree::Args[
51
+ parts: [
52
+ SyntaxTree::CallNode[
53
+ receiver: nil,
54
+ operator: nil,
55
+ message: SyntaxTree::Ident[value: "eq"],
56
+ arguments: SyntaxTree::ArgParen[
57
+ arguments: SyntaxTree::Args[
58
+ parts: [SyntaxTree::Int[value: "2"]]
59
+ ]
60
+ ]
61
+ ]
62
+ ]
63
+ ]
64
+ ]
65
+ ]
66
+ ]
67
+ ]
68
+ ```
69
+
70
+ We want the top level node of the body. Remove the SyntaxTree namespaces and take just enough to identify the top node
71
+ ```ruby
72
+ expect_eq_search =
73
+ 'CommandCall[
74
+ receiver: CallNode[
75
+ message: Ident[value: "expect"]
76
+ ],
77
+ operator: Period[value: "."],
78
+ message: Ident[value: "to"],
79
+ arguments: Args[
80
+ parts: [ CallNode[
81
+ message: Ident[value: "eq"]
82
+ ]
83
+ ]
84
+ ]
85
+ ]'
86
+ ```
87
+
88
+ And use that as our argument to `visitor.mutate`
89
+ ```ruby
90
+ visitor.mutate(expect_eq_search) do
91
+ # ...
92
+ end
93
+ ```
94
+
95
+ For the block body, start by pulling out relevant information with a pattern matching statement from construct_keys
96
+ TODO pare down the pattern match and extract variables
97
+ ```ruby
98
+ node => SyntaxTree::CommandCall[
99
+ receiver: SyntaxTree::CallNode[
100
+ receiver: nil,
101
+ operator: nil,
102
+ message: SyntaxTree::Ident[value: "expect"],
103
+ arguments: SyntaxTree::ArgParen[
104
+ arguments: SyntaxTree::Args[parts: [actual_expr]]
105
+ ]
106
+ ],
107
+ operator: SyntaxTree::Period[value: "."],
108
+ message: SyntaxTree::Ident[value: "to"],
109
+ arguments: SyntaxTree::Args[
110
+ parts: [
111
+ SyntaxTree::CallNode[
112
+ receiver: nil,
113
+ operator: nil,
114
+ message: SyntaxTree::Ident[value: "eq"],
115
+ arguments: SyntaxTree::ArgParen[
116
+ arguments: SyntaxTree::Args[parts: [expected_expr]]
117
+ ]
118
+ ]
119
+ ]
120
+ ]
121
+ ]
122
+ ```
123
+
124
+ Now write the code you want to transform it to and construct the nodes
125
+
126
+ ```ruby
127
+ require "syntax_tree"
128
+
129
+ code = <<~RUBY
130
+ assert_equal(2, calculator.add(1, 1))
131
+ RUBY
132
+
133
+ program = SyntaxTree.parse code
134
+ puts program.construct_keys
135
+ ```
136
+
137
+ Again you get a bunch of output
138
+ ```ruby
139
+ SyntaxTree::Program[
140
+ statements: SyntaxTree::Statements[
141
+ body: [
142
+ SyntaxTree::CallNode[
143
+ receiver: nil,
144
+ operator: nil,
145
+ message: SyntaxTree::Ident[value: "assert_equal"],
146
+ arguments: SyntaxTree::ArgParen[
147
+ arguments: SyntaxTree::Args[
148
+ parts: [
149
+ SyntaxTree::Int[value: "2"],
150
+ SyntaxTree::CallNode[
151
+ receiver: SyntaxTree::VCall[
152
+ value: SyntaxTree::Ident[value: "calculator"]
153
+ ],
154
+ operator: SyntaxTree::Period[value: "."],
155
+ message: SyntaxTree::Ident[value: "add"],
156
+ arguments: SyntaxTree::ArgParen[
157
+ arguments: SyntaxTree::Args[
158
+ parts: [
159
+ SyntaxTree::Int[value: "1"],
160
+ SyntaxTree::Int[value: "1"]
161
+ ]
162
+ ]
163
+ ]
164
+ ]
165
+ ]
166
+ ]
167
+ ]
168
+ ]
169
+ ]
170
+ ]
171
+ ]
172
+ ```
173
+
174
+ Add `.new` and convert parentheses. You'll have to add `location: node.location` where required (trial and error). Make this node tree the return value of the mutate block.
175
+
176
+ ```ruby
177
+ expect_eq_search =
178
+ 'CommandCall[
179
+ receiver: CallNode[
180
+ message: Ident[value: "expect"]
181
+ ],
182
+ operator: Period[value: "."],
183
+ message: Ident[value: "to"],
184
+ arguments: Args[
185
+ parts: [ CallNode[
186
+ message: Ident[value: "eq"]
187
+ ]
188
+ ]
189
+ ]
190
+ ]'
191
+ visitor.mutate(expect_eq_search) do |node|
192
+ node => SyntaxTree::CommandCall[
193
+ receiver: SyntaxTree::CallNode[
194
+ receiver: nil,
195
+ operator: nil,
196
+ message: SyntaxTree::Ident[value: "expect"],
197
+ arguments: SyntaxTree::ArgParen[
198
+ arguments: SyntaxTree::Args[parts: [actual_expr]]
199
+ ]
200
+ ],
201
+ operator: SyntaxTree::Period[value: "."],
202
+ message: SyntaxTree::Ident[value: "to"],
203
+ arguments: SyntaxTree::Args[
204
+ parts: [
205
+ SyntaxTree::CallNode[
206
+ receiver: nil,
207
+ operator: nil,
208
+ message: SyntaxTree::Ident[value: "eq"],
209
+ arguments: SyntaxTree::ArgParen[
210
+ arguments: SyntaxTree::Args[parts: [expected_expr]]
211
+ ]
212
+ ]
213
+ ]
214
+ ]
215
+ ]
216
+
217
+ SyntaxTree::CallNode.new(
218
+ message:
219
+ SyntaxTree::Ident.new(
220
+ value: "assert_equal",
221
+ location: node.location
222
+ ),
223
+ arguments:
224
+ SyntaxTree::ArgParen.new(
225
+ arguments:
226
+ SyntaxTree::Args.new(
227
+ parts: [expected_expr, actual_expr],
228
+ location: node.location
229
+ ),
230
+ location: node.location
231
+ ),
232
+ location: node.location,
233
+ receiver: nil,
234
+ operator: nil
235
+ )
236
+ end
237
+ ```
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minitestify
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafe Rosen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-23 00:00:00.000000000 Z
11
+ date: 2022-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: syntax_tree
@@ -24,10 +24,39 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-inflector
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-cli
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
27
55
  description:
28
56
  email:
29
57
  - rafe@existentialmutt.com
30
- executables: []
58
+ executables:
59
+ - minitestify
31
60
  extensions: []
32
61
  extra_rdoc_files: []
33
62
  files:
@@ -38,9 +67,13 @@ files:
38
67
  - LICENSE.txt
39
68
  - README.md
40
69
  - Rakefile
70
+ - exe/minitestify
41
71
  - lib/minitestify.rb
72
+ - lib/minitestify/cli.rb
73
+ - lib/minitestify/spec.rb
42
74
  - lib/minitestify/version.rb
43
75
  - minitestify.gemspec
76
+ - scripts/transform.md
44
77
  - sig/minitestify.rbs
45
78
  homepage: https://github.com/existentialmutt/minitestify
46
79
  licenses: